Merge branch 'feature/store' into dynamic_field

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-11-21 15:04:44 -03:00
commit 4959d8ecf4
224 changed files with 7150 additions and 3415 deletions

View file

@ -71,3 +71,15 @@ LANGFLOW_SUPERUSER=
# Superuser password # Superuser password
# Example: LANGFLOW_SUPERUSER_PASSWORD=123456 # Example: LANGFLOW_SUPERUSER_PASSWORD=123456
LANGFLOW_SUPERUSER_PASSWORD= LANGFLOW_SUPERUSER_PASSWORD=
# STORE_URL
# Example: LANGFLOW_STORE_URL=https://api.langflow.store
LANGFLOW_STORE_URL=
# DOWNLOAD_WEBHOOK_URL
#
LANGFLOW_DOWNLOAD_WEBHOOK_URL=
# LIKE_WEBHOOK_URL
#
LANGFLOW_LIKE_WEBHOOK_URL=

3
.vscode/launch.json vendored
View file

@ -16,7 +16,8 @@
"debug" "debug"
], ],
"jinja": true, "jinja": true,
"justMyCode": true "justMyCode": true,
"envFile": "${workspaceFolder}/.env"
}, },
{ {
"name": "Python: Remote Attach", "name": "Python: Remote Attach",

View file

@ -30,13 +30,12 @@ else
endif endif
format: format:
poetry run black .
poetry run ruff . --fix poetry run ruff . --fix
poetry run ruff format .
cd src/frontend && npm run format cd src/frontend && npm run format
lint: lint:
poetry run mypy src/backend/langflow poetry run mypy src/backend/langflow
poetry run black . --check
poetry run ruff . --fix poetry run ruff . --fix
install_frontend: install_frontend:
@ -46,6 +45,7 @@ install_frontendc:
cd src/frontend && rm -rf node_modules package-lock.json && npm install cd src/frontend && rm -rf node_modules package-lock.json && npm install
run_frontend: run_frontend:
@-kill -9 `lsof -t -i:3000`
cd src/frontend && npm start cd src/frontend && npm start
run_cli: run_cli:
@ -73,12 +73,13 @@ install_backend:
backend: backend:
make install_backend make install_backend
@-kill -9 `lsof -t -i:7860`
ifeq ($(login),1) ifeq ($(login),1)
@echo "Running backend without autologin"; @echo "Running backend without autologin";
poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --log-level debug --workers 3 poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env
else else
@echo "Running backend with autologin"; @echo "Running backend with autologin";
LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --log-level debug --workers 3 LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env
endif endif
build_and_run: build_and_run:

932
package-lock.json generated Normal file
View file

@ -0,0 +1,932 @@
{
"name": "langflow",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@radix-ui/react-popover": "^1.0.7",
"cmdk": "^0.2.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.23.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
"dependencies": {
"@floating-ui/utils": "^0.1.3"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
"dependencies": {
"@floating-ui/core": "^1.4.2",
"@floating-ui/utils": "^0.1.3"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
"integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
"dependencies": {
"@floating-ui/dom": "^1.5.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
},
"node_modules/@radix-ui/primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
"integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-context": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz",
"integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.0",
"@radix-ui/react-focus-guards": "1.0.0",
"@radix-ui/react-focus-scope": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-portal": "1.0.0",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-slot": "1.0.0",
"@radix-ui/react-use-controllable-state": "1.0.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.4"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0",
"@radix-ui/react-use-escape-keydown": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-primitive": "1.0.0",
"@radix-ui/react-use-callback-ref": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
"dependencies": {
"react-remove-scroll-bar": "^2.3.3",
"react-style-singleton": "^2.2.1",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.0",
"use-sidecar": "^1.1.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-callback-ref": "1.0.1",
"@radix-ui/react-use-escape-keydown": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-focus-scope": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-callback-ref": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-id": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
"integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-dismissable-layer": "1.0.5",
"@radix-ui/react-focus-guards": "1.0.1",
"@radix-ui/react-focus-scope": "1.0.4",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-popper": "1.1.3",
"@radix-ui/react-portal": "1.0.4",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-use-controllable-state": "1.0.1",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.5"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz",
"integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@floating-ui/react-dom": "^2.0.0",
"@radix-ui/react-arrow": "1.0.3",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-callback-ref": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1",
"@radix-ui/react-use-rect": "1.0.1",
"@radix-ui/react-use-size": "1.0.1",
"@radix-ui/rect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-portal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-presence": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-controllable-state": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-rect": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz",
"integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/rect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-size": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz",
"integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/rect": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz",
"integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/aria-hidden": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz",
"integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/cmdk": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.0.tgz",
"integrity": "sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==",
"dependencies": {
"@radix-ui/react-dialog": "1.0.0",
"command-score": "0.1.2"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
},
"node_modules/command-score": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz",
"integrity": "sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w=="
},
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
},
"node_modules/get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
"engines": {
"node": ">=6"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/react-remove-scroll": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
"dependencies": {
"react-remove-scroll-bar": "^2.3.3",
"react-style-singleton": "^2.2.1",
"tslib": "^2.1.0",
"use-callback-ref": "^1.3.0",
"use-sidecar": "^1.1.2"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-remove-scroll-bar": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz",
"integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==",
"dependencies": {
"react-style-singleton": "^2.2.1",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-style-singleton": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
"integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
"dependencies": {
"get-nonce": "^1.0.0",
"invariant": "^2.2.4",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/use-callback-ref": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
"integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sidecar": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
"integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
"dependencies": {
"detect-node-es": "^1.1.0",
"tslib": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
}
}
}

6
package.json Normal file
View file

@ -0,0 +1,6 @@
{
"dependencies": {
"@radix-ui/react-popover": "^1.0.7",
"cmdk": "^0.2.0"
}
}

44
poetry.lock generated
View file

@ -410,48 +410,6 @@ files = [
{file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"}, {file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"},
] ]
[[package]]
name = "black"
version = "23.11.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"},
{file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"},
{file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"},
{file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"},
{file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"},
{file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"},
{file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"},
{file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"},
{file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"},
{file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"},
{file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"},
{file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"},
{file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"},
{file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"},
{file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"},
{file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"},
{file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"},
{file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "blinker" name = "blinker"
version = "1.7.0" version = "1.7.0"
@ -8927,4 +8885,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "27f6f86ca62ee5cfdae333daae4f043a745ed2fff0d958a201b514fac36068d4" content-hash = "ab18ac504d15c9a85e1fe6457df0bef60aeeb96ed78d7281fbe3eb7c9017c8f4"

View file

@ -87,7 +87,7 @@ passlib = "^1.7.4"
bcrypt = "^4.0.1" bcrypt = "^4.0.1"
python-jose = "^3.3.0" python-jose = "^3.3.0"
metaphor-python = "^0.1.11" metaphor-python = "^0.1.11"
pydantic = "^2.4.0" pydantic = "^2.0.0"
pydantic-settings = "^2.0.3" pydantic-settings = "^2.0.3"
zep-python = { version = "^1.3.0", allow-prereleases = true } zep-python = { version = "^1.3.0", allow-prereleases = true }
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" } pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
@ -105,9 +105,8 @@ pgvector = "^0.2.3"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
types-redis = "^4.6.0.5" types-redis = "^4.6.0.5"
black = "^23.10.0"
ipykernel = "^6.21.2" ipykernel = "^6.21.2"
mypy = "^1.6.1" mypy = "^1.1.1"
ruff = "^0.1.5" ruff = "^0.1.5"
httpx = "*" httpx = "*"
pytest = "^7.4.2" pytest = "^7.4.2"
@ -144,6 +143,7 @@ markers = ["async_test"]
[tool.ruff] [tool.ruff]
exclude = ["src/backend/langflow/alembic/*"]
line-length = 120 line-length = 120
[build-system] [build-system]

View file

@ -11,7 +11,7 @@ import typer
from dotenv import load_dotenv from dotenv import load_dotenv
from langflow.main import setup_app from langflow.main import setup_app
from langflow.services.database.utils import session_getter from langflow.services.database.utils import session_getter
from langflow.services.getters import get_db_service, get_settings_service from langflow.services.deps import get_db_service, get_settings_service
from langflow.services.utils import initialize_services, initialize_settings_service from langflow.services.utils import initialize_services, initialize_settings_service
from langflow.utils.logger import configure, logger from langflow.utils.logger import configure, logger
from multiprocess import Process, cpu_count # type: ignore from multiprocess import Process, cpu_count # type: ignore
@ -72,6 +72,7 @@ def update_settings(
dev: bool = False, dev: bool = False,
remove_api_keys: bool = False, remove_api_keys: bool = False,
components_path: Optional[Path] = None, components_path: Optional[Path] = None,
store: bool = True,
): ):
"""Update the settings from a config file.""" """Update the settings from a config file."""
@ -90,16 +91,15 @@ def update_settings(
if components_path: if components_path:
logger.debug(f"Adding component path {components_path}") logger.debug(f"Adding component path {components_path}")
settings_service.settings.update_settings(COMPONENTS_PATH=components_path) settings_service.settings.update_settings(COMPONENTS_PATH=components_path)
if not store:
logger.debug("Setting store to False")
settings_service.settings.update_settings(STORE=False)
@app.command() @app.command()
def run( def run(
host: str = typer.Option( host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
"127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST" workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
),
workers: int = typer.Option(
1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"
),
timeout: int = typer.Option(300, help="Worker timeout in seconds."), timeout: int = typer.Option(300, help="Worker timeout in seconds."),
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"), port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
components_path: Optional[Path] = typer.Option( components_path: Optional[Path] = typer.Option(
@ -107,32 +107,17 @@ def run(
help="Path to the directory containing custom components.", help="Path to the directory containing custom components.",
envvar="LANGFLOW_COMPONENTS_PATH", envvar="LANGFLOW_COMPONENTS_PATH",
), ),
config: str = typer.Option( config: str = typer.Option(Path(__file__).parent / "config.yaml", help="Path to the configuration file."),
Path(__file__).parent / "config.yaml", help="Path to the configuration file."
),
# .env file param # .env file param
env_file: Path = typer.Option( env_file: Path = typer.Option(None, help="Path to the .env file containing environment variables."),
None, help="Path to the .env file containing environment variables." log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
), log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"),
log_level: str = typer.Option(
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
),
log_file: Path = typer.Option(
"logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"
),
cache: Optional[str] = typer.Option( cache: Optional[str] = typer.Option(
envvar="LANGFLOW_LANGCHAIN_CACHE", envvar="LANGFLOW_LANGCHAIN_CACHE",
help="Type of cache to use. (InMemoryCache, SQLiteCache)", help="Type of cache to use. (InMemoryCache, SQLiteCache)",
default=None, default=None,
), ),
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"), dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
# This variable does not work but is set by the .env file
# and works with Pydantic
# database_url: str = typer.Option(
# None,
# help="Database URL to connect to. If not provided, a local SQLite database will be used.",
# envvar="LANGFLOW_DATABASE_URL",
# ),
path: str = typer.Option( path: str = typer.Option(
None, None,
help="Path to the frontend directory containing build files. This is for development purposes only.", help="Path to the frontend directory containing build files. This is for development purposes only.",
@ -153,6 +138,11 @@ def run(
help="Run only the backend server without the frontend.", help="Run only the backend server without the frontend.",
envvar="LANGFLOW_BACKEND_ONLY", envvar="LANGFLOW_BACKEND_ONLY",
), ),
store: bool = typer.Option(
True,
help="Enables the store features.",
envvar="LANGFLOW_STORE",
),
): ):
""" """
Run the Langflow. Run the Langflow.
@ -171,6 +161,7 @@ def run(
remove_api_keys=remove_api_keys, remove_api_keys=remove_api_keys,
cache=cache, cache=cache,
components_path=components_path, components_path=components_path,
store=store,
) )
# create path object if path is provided # create path object if path is provided
static_files_dir: Optional[Path] = Path(path) if path else None static_files_dir: Optional[Path] = Path(path) if path else None
@ -200,9 +191,7 @@ def run(
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True): def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
webapp_process = Process( webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
target=run_langflow, args=(host, port, log_level, options, app)
)
webapp_process.start() webapp_process.start()
status_code = 0 status_code = 0
while status_code != 200: while status_code != 200:
@ -278,9 +267,7 @@ def print_banner(host, port):
) )
# Create a panel with the title and the info text, and a border around it # Create a panel with the title and the info text, and a border around it
panel = Panel( panel = Panel(f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False)
f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False
)
# Print the banner with a separator line before and after # Print the banner with a separator line before and after
rprint(panel) rprint(panel)
@ -312,12 +299,8 @@ def run_langflow(host, port, log_level, options, app):
@app.command() @app.command()
def superuser( def superuser(
username: str = typer.Option(..., prompt=True, help="Username for the superuser."), username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
password: str = typer.Option( password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
..., prompt=True, hide_input=True, help="Password for the superuser." log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
),
log_level: str = typer.Option(
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
),
): ):
""" """
Create a superuser. Create a superuser.

View file

@ -5,7 +5,7 @@ from sqlalchemy import pool
from alembic import context from alembic import context
from langflow.services.database.manager import SQLModel from langflow.services.database.service import SQLModel
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.

View file

@ -0,0 +1,48 @@
"""Store updates
Revision ID: 7843803a87b5
Revises: eb5866d51fd2
Create Date: 2023-10-18 23:08:57.744906
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = "7843803a87b5"
down_revision: Union[str, None] = "eb5866d51fd2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
try:
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"store_api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=True
)
)
except Exception:
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.drop_column("store_api_key")
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.drop_column("is_component")
# ### end Alembic commands ###

View file

@ -0,0 +1,45 @@
"""User id can be null in Flow
Revision ID: f5ee9749d1a6
Revises: 7843803a87b5
Create Date: 2023-10-18 23:12:27.297016
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
# revision identifiers, used by Alembic.
revision: str = "f5ee9749d1a6"
down_revision: Union[str, None] = "7843803a87b5"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
try:
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.alter_column(
"user_id", existing_type=sa.CHAR(length=32), nullable=True
)
except Exception:
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
try:
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.alter_column(
"user_id", existing_type=sa.CHAR(length=32), nullable=False
)
except Exception:
pass
# ### end Alembic commands ###

View file

@ -5,7 +5,7 @@ from langflow.api.v1 import (
endpoints_router, endpoints_router,
validate_router, validate_router,
flows_router, flows_router,
component_router, store_router,
users_router, users_router,
api_key_router, api_key_router,
login_router, login_router,
@ -17,7 +17,7 @@ router = APIRouter(
router.include_router(chat_router) router.include_router(chat_router)
router.include_router(endpoints_router) router.include_router(endpoints_router)
router.include_router(validate_router) router.include_router(validate_router)
router.include_router(component_router) router.include_router(store_router)
router.include_router(flows_router) router.include_router(flows_router)
router.include_router(users_router) router.include_router(users_router)
router.include_router(api_key_router) router.include_router(api_key_router)

View file

@ -2,9 +2,7 @@ API_WORDS = ["api", "key", "token"]
def has_api_terms(word: str): def has_api_terms(word: str):
return "api" in word and ( return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
"key" in word or ("token" in word and "tokens" not in word)
)
def remove_api_keys(flow: dict): def remove_api_keys(flow: dict):
@ -14,11 +12,7 @@ def remove_api_keys(flow: dict):
node_data = node.get("data").get("node") node_data = node.get("data").get("node")
template = node_data.get("template") template = node_data.get("template")
for value in template.values(): for value in template.values():
if ( if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
isinstance(value, dict)
and has_api_terms(value["name"])
and value.get("password")
):
value["value"] = None value["value"] = None
return flow return flow
@ -39,9 +33,7 @@ def build_input_keys_response(langchain_object, artifacts):
input_keys_response["input_keys"][key] = value input_keys_response["input_keys"][key] = value
# If the object has memory, that memory will have a memory_variables attribute # If the object has memory, that memory will have a memory_variables attribute
# memory variables should be removed from the input keys # memory variables should be removed from the input keys
if hasattr(langchain_object, "memory") and hasattr( if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
langchain_object.memory, "memory_variables"
):
# Remove memory variables from input keys # Remove memory variables from input keys
input_keys_response["input_keys"] = { input_keys_response["input_keys"] = {
key: value key: value
@ -51,9 +43,7 @@ def build_input_keys_response(langchain_object, artifacts):
# Add memory variables to memory_keys # Add memory variables to memory_keys
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
if hasattr(langchain_object, "prompt") and hasattr( if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
langchain_object.prompt, "template"
):
input_keys_response["template"] = langchain_object.prompt.template input_keys_response["template"] = langchain_object.prompt.template
return input_keys_response return input_keys_response

View file

@ -2,7 +2,7 @@ from langflow.api.v1.endpoints import router as endpoints_router
from langflow.api.v1.validate import router as validate_router from langflow.api.v1.validate import router as validate_router
from langflow.api.v1.chat import router as chat_router from langflow.api.v1.chat import router as chat_router
from langflow.api.v1.flows import router as flows_router from langflow.api.v1.flows import router as flows_router
from langflow.api.v1.components import router as component_router from langflow.api.v1.store import router as store_router
from langflow.api.v1.users import router as users_router from langflow.api.v1.users import router as users_router
from langflow.api.v1.api_key import router as api_key_router from langflow.api.v1.api_key import router as api_key_router
from langflow.api.v1.login import router as login_router from langflow.api.v1.login import router as login_router
@ -10,7 +10,7 @@ from langflow.api.v1.login import router as login_router
__all__ = [ __all__ = [
"chat_router", "chat_router",
"endpoints_router", "endpoints_router",
"component_router", "store_router",
"validate_router", "validate_router",
"flows_router", "flows_router",
"users_router", "users_router",

View file

@ -1,7 +1,7 @@
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
from langflow.api.v1.schemas import ApiKeysResponse from langflow.api.v1.schemas import ApiKeysResponse, ApiKeyCreateRequest
from langflow.services.auth.utils import get_current_active_user from langflow.services.auth import utils as auth_utils
from langflow.services.database.models.api_key.api_key import ( from langflow.services.database.models.api_key.api_key import (
ApiKeyCreate, ApiKeyCreate,
UnmaskedApiKeyRead, UnmaskedApiKeyRead,
@ -14,9 +14,17 @@ from langflow.services.database.models.api_key.crud import (
delete_api_key, delete_api_key,
) )
from langflow.services.database.models.user.user import User from langflow.services.database.models.user.user import User
from langflow.services.getters import get_session from langflow.services.deps import (
get_session,
get_settings_service,
)
from typing import TYPE_CHECKING
from sqlmodel import Session from sqlmodel import Session
if TYPE_CHECKING:
pass
router = APIRouter(tags=["APIKey"], prefix="/api_key") router = APIRouter(tags=["APIKey"], prefix="/api_key")
@ -24,7 +32,7 @@ router = APIRouter(tags=["APIKey"], prefix="/api_key")
@router.get("/", response_model=ApiKeysResponse) @router.get("/", response_model=ApiKeysResponse)
def get_api_keys_route( def get_api_keys_route(
db: Session = Depends(get_session), db: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user), current_user: User = Depends(auth_utils.get_current_active_user),
): ):
try: try:
user_id = current_user.id user_id = current_user.id
@ -38,7 +46,7 @@ def get_api_keys_route(
@router.post("/", response_model=UnmaskedApiKeyRead) @router.post("/", response_model=UnmaskedApiKeyRead)
def create_api_key_route( def create_api_key_route(
req: ApiKeyCreate, req: ApiKeyCreate,
current_user: User = Depends(get_current_active_user), current_user: User = Depends(auth_utils.get_current_active_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
try: try:
@ -51,7 +59,7 @@ def create_api_key_route(
@router.delete("/{api_key_id}") @router.delete("/{api_key_id}")
def delete_api_key_route( def delete_api_key_route(
api_key_id: UUID, api_key_id: UUID,
current_user=Depends(get_current_active_user), current_user=Depends(auth_utils.get_current_active_user),
db: Session = Depends(get_session), db: Session = Depends(get_session),
): ):
try: try:
@ -59,3 +67,21 @@ def delete_api_key_route(
return {"detail": "API Key deleted"} return {"detail": "API Key deleted"}
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e raise HTTPException(status_code=400, detail=str(e)) from e
@router.post("/store")
def save_store_api_key(
api_key_request: ApiKeyCreateRequest,
current_user: User = Depends(auth_utils.get_current_active_user),
db: Session = Depends(get_session),
settings_service=Depends(get_settings_service),
):
try:
api_key = api_key_request.api_key
# Encrypt the API key
encrypted = auth_utils.encrypt_api_key(api_key, settings_service=settings_service)
current_user.store_api_key = encrypted
db.commit()
return {"detail": "API Key saved"}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e

View file

@ -81,9 +81,7 @@ def validate_prompt(template: str):
# Check if there are invalid characters in the input_variables # Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables) input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables): if any(var in INVALID_NAMES for var in input_variables):
raise ValueError( raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
)
try: try:
PromptTemplate(template=template, input_variables=input_variables) PromptTemplate(template=template, input_variables=input_variables)
@ -134,9 +132,7 @@ def check_input_variables(input_variables: list):
return input_variables return input_variables
def build_error_message( def build_error_message(input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables):
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
):
input_variables_str = ", ".join([f"'{var}'" for var in input_variables]) input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
error_string = f"Invalid input variables: {input_variables_str}. " error_string = f"Invalid input variables: {input_variables_str}. "

View file

@ -1,19 +1,15 @@
import asyncio import asyncio
from typing import Any, Dict, List, Optional
from uuid import UUID from uuid import UUID
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langflow.api.v1.schemas import ChatResponse, PromptResponse
from typing import Any, Dict, List, Optional
from langflow.services.getters import get_chat_service
from langflow.utils.util import remove_ansi_escape_codes
from langchain.schema import AgentAction, AgentFinish from langchain.schema import AgentAction, AgentFinish
from loguru import logger from loguru import logger
from langflow.api.v1.schemas import ChatResponse, PromptResponse
from langflow.services.deps import get_chat_service
from langflow.utils.util import remove_ansi_escape_codes
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py # https://github.com/hwchase17/chat-langchain/blob/master/callback.py
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
@ -26,18 +22,16 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
resp = ChatResponse(message=token, type="stream", intermediate_steps="") resp = ChatResponse(message=token, type="stream", intermediate_steps="")
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
async def on_tool_start( async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> Any:
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
) -> Any:
"""Run when tool starts running.""" """Run when tool starts running."""
resp = ChatResponse( resp = ChatResponse(
message="", message="",
type="stream", type="stream",
intermediate_steps=f"Tool input: {input_str}", intermediate_steps=f"Tool input: {input_str}",
) )
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
async def on_tool_end(self, output: str, **kwargs: Any) -> Any: async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
"""Run when tool ends running.""" """Run when tool ends running."""
@ -68,7 +62,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
try: try:
# This is to emulate the stream of tokens # This is to emulate the stream of tokens
for resp in resps: for resp in resps:
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
except Exception as exc: except Exception as exc:
logger.error(f"Error sending response: {exc}") logger.error(f"Error sending response: {exc}")
@ -94,7 +88,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
resp = PromptResponse( resp = PromptResponse(
prompt=text, prompt=text,
) )
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
self.chat_service.chat_history.add_message(self.client_id, resp) self.chat_service.chat_history.add_message(self.client_id, resp)
async def on_agent_action(self, action: AgentAction, **kwargs: Any): async def on_agent_action(self, action: AgentAction, **kwargs: Any):
@ -105,10 +99,10 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
logs = log.split("\n") logs = log.split("\n")
for log in logs: for log in logs:
resp = ChatResponse(message="", type="stream", intermediate_steps=log) resp = ChatResponse(message="", type="stream", intermediate_steps=log)
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
else: else:
resp = ChatResponse(message="", type="stream", intermediate_steps=log) resp = ChatResponse(message="", type="stream", intermediate_steps=log)
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any: async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
"""Run on agent end.""" """Run on agent end."""
@ -117,7 +111,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
type="stream", type="stream",
intermediate_steps=finish.log, intermediate_steps=finish.log,
) )
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.model_dump())
class StreamingLLMCallbackHandler(BaseCallbackHandler): class StreamingLLMCallbackHandler(BaseCallbackHandler):
@ -132,5 +126,5 @@ class StreamingLLMCallbackHandler(BaseCallbackHandler):
resp = ChatResponse(message=token, type="stream", intermediate_steps="") resp = ChatResponse(message=token, type="stream", intermediate_steps="")
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
coroutine = self.websocket.send_json(resp.dict()) coroutine = self.websocket.send_json(resp.model_dump())
asyncio.run_coroutine_threadsafe(coroutine, loop) asyncio.run_coroutine_threadsafe(coroutine, loop)

View file

@ -18,10 +18,10 @@ from langflow.services.auth.utils import (
) )
from langflow.services.cache.utils import update_build_status from langflow.services.cache.utils import update_build_status
from loguru import logger from loguru import logger
from langflow.services.getters import get_chat_service, get_session, get_cache_service from langflow.services.deps import get_chat_service, get_session, get_cache_service
from sqlmodel import Session from sqlmodel import Session
from langflow.services.chat.manager import ChatService from langflow.services.chat.service import ChatService
from langflow.services.cache.manager import BaseCacheService from langflow.services.cache.service import BaseCacheService
router = APIRouter(tags=["Chat"]) router = APIRouter(tags=["Chat"])
@ -40,13 +40,9 @@ async def chat(
user = await get_current_user_by_jwt(token, db) user = await get_current_user_by_jwt(token, db)
await websocket.accept() await websocket.accept()
if not user: if not user:
await websocket.close( await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
)
if not user.is_active: if not user.is_active:
await websocket.close( await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
)
if client_id in chat_service.cache_service: if client_id in chat_service.cache_service:
await chat_service.handle_websocket(client_id, websocket) await chat_service.handle_websocket(client_id, websocket)
@ -62,9 +58,7 @@ async def chat(
logger.error(f"Error in chat websocket: {exc}") logger.error(f"Error in chat websocket: {exc}")
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc) messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
if "Could not validate credentials" in str(exc): if "Could not validate credentials" in str(exc):
await websocket.close( await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
)
else: else:
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
@ -106,15 +100,10 @@ async def init_build(
@router.get("/build/{flow_id}/status", response_model=BuiltResponse) @router.get("/build/{flow_id}/status", response_model=BuiltResponse)
async def build_status( async def build_status(flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)):
flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)
):
"""Check the flow_id is in the cache_service.""" """Check the flow_id is in the cache_service."""
try: try:
built = ( built = flow_id in cache_service and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
flow_id in cache_service
and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
)
return BuiltResponse( return BuiltResponse(
built=built, built=built,
@ -181,9 +170,7 @@ async def stream_build(
params = vertex._built_object_repr() params = vertex._built_object_repr()
valid = True valid = True
logger.debug(f"Building node {str(vertex.vertex_type)}") logger.debug(f"Building node {str(vertex.vertex_type)}")
logger.debug( logger.debug(f"Output: {params[:100]}{'...' if len(params) > 100 else ''}")
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
)
if vertex.artifacts: if vertex.artifacts:
# The artifacts will be prompt variables # The artifacts will be prompt variables
# passed to build_input_keys_response # passed to build_input_keys_response
@ -195,9 +182,7 @@ async def stream_build(
valid = False valid = False
update_build_status(cache_service, flow_id, BuildStatus.FAILURE) update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
vertex_id = ( vertex_id = vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
)
if vertex_id in graph.top_level_nodes: if vertex_id in graph.top_level_nodes:
response = { response = {
"valid": valid, "valid": valid,
@ -211,9 +196,7 @@ async def stream_build(
langchain_object = graph.build() langchain_object = graph.build()
# Now we need to check the input_keys to send them to the client # Now we need to check the input_keys to send them to the client
if hasattr(langchain_object, "input_keys"): if hasattr(langchain_object, "input_keys"):
input_keys_response = build_input_keys_response( input_keys_response = build_input_keys_response(langchain_object, artifacts)
langchain_object, artifacts
)
else: else:
input_keys_response = { input_keys_response = {
"input_keys": None, "input_keys": None,

View file

@ -1,77 +0,0 @@
from datetime import timezone
from typing import List
from uuid import UUID
from langflow.services.database.models.component import Component, ComponentModel
from langflow.services.getters import get_session
from sqlmodel import Session, select
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from datetime import datetime
COMPONENT_NOT_FOUND = "Component not found"
COMPONENT_ALREADY_EXISTS = "A component with the same id already exists."
COMPONENT_DELETED = "Component deleted"
router = APIRouter(prefix="/components", tags=["Components"])
@router.post("/", response_model=Component)
def create_component(component: ComponentModel, db: Session = Depends(get_session)):
db_component = Component(**component.dict())
try:
db.add(db_component)
db.commit()
db.refresh(db_component)
except IntegrityError as e:
db.rollback()
raise HTTPException(
status_code=400,
detail=COMPONENT_ALREADY_EXISTS,
) from e
return db_component
@router.get("/{component_id}", response_model=Component)
def read_component(component_id: UUID, db: Session = Depends(get_session)):
if component := db.get(Component, component_id):
return component
else:
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
@router.get("/", response_model=List[Component])
def read_components(skip: int = 0, limit: int = 50, db: Session = Depends(get_session)):
query = select(Component)
query = query.offset(skip).limit(limit)
return db.execute(query).fetchall()
@router.patch("/{component_id}", response_model=Component)
def update_component(
component_id: UUID, component: ComponentModel, db: Session = Depends(get_session)
):
db_component = db.get(Component, component_id)
if not db_component:
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
component_data = component.dict(exclude_unset=True)
for key, value in component_data.items():
setattr(db_component, key, value)
db_component.update_at = datetime.now(timezone.utc)
db.commit()
db.refresh(db_component)
return db_component
@router.delete("/{component_id}")
def delete_component(component_id: UUID, db: Session = Depends(get_session)):
component = db.get(Component, component_id)
if not component:
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
db.delete(component)
db.commit()
return {"detail": COMPONENT_DELETED}

View file

@ -7,7 +7,7 @@ from langflow.services.cache.utils import save_uploaded_file
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
from langflow.processing.process import process_graph_cached, process_tweaks from langflow.processing.process import process_graph_cached, process_tweaks
from langflow.services.database.models.user.user import User from langflow.services.database.models.user.user import User
from langflow.services.getters import ( from langflow.services.deps import (
get_session_service, get_session_service,
get_settings_service, get_settings_service,
get_task_service, get_task_service,
@ -27,7 +27,7 @@ from langflow.api.v1.schemas import (
) )
from langflow.services.getters import get_session from langflow.services.deps import get_session
try: try:
from langflow.worker import process_graph_cached_task from langflow.worker import process_graph_cached_task
@ -40,7 +40,7 @@ except ImportError:
from sqlmodel import Session from sqlmodel import Session
from langflow.services.task.manager import TaskService from langflow.services.task.service import TaskService
# build router # build router
router = APIRouter(tags=["Base"]) router = APIRouter(tags=["Base"])

View file

@ -1,6 +1,10 @@
from typing import List from typing import List
from uuid import UUID from uuid import UUID
import orjson
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from sqlmodel import Session
from langflow.api.utils import remove_api_keys from langflow.api.utils import remove_api_keys
from langflow.api.v1.schemas import FlowListCreate, FlowListRead from langflow.api.v1.schemas import FlowListCreate, FlowListRead
@ -12,13 +16,7 @@ from langflow.services.database.models.flow import (
FlowUpdate, FlowUpdate,
) )
from langflow.services.database.models.user.user import User from langflow.services.database.models.user.user import User
from langflow.services.getters import get_session from langflow.services.deps import get_session, get_settings_service
from langflow.services.getters import get_settings_service
import orjson
from sqlmodel import Session
from fastapi import APIRouter, Depends, HTTPException
from fastapi import File, UploadFile
# build router # build router
router = APIRouter(prefix="/flows", tags=["Flows"]) router = APIRouter(prefix="/flows", tags=["Flows"])
@ -46,7 +44,6 @@ def create_flow(
@router.get("/", response_model=list[FlowRead], status_code=200) @router.get("/", response_model=list[FlowRead], status_code=200)
def read_flows( def read_flows(
*, *,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user), current_user: User = Depends(get_current_active_user),
): ):
"""Read all flows.""" """Read all flows."""
@ -65,12 +62,7 @@ def read_flow(
current_user: User = Depends(get_current_active_user), current_user: User = Depends(get_current_active_user),
): ):
"""Read a flow.""" """Read a flow."""
if user_flow := ( if user_flow := (session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == current_user.id).first()):
session.query(Flow)
.filter(Flow.id == flow_id)
.filter(Flow.user_id == current_user.id)
.first()
):
return user_flow return user_flow
else: else:
raise HTTPException(status_code=404, detail="Flow not found") raise HTTPException(status_code=404, detail="Flow not found")
@ -169,5 +161,5 @@ async def download_file(
current_user: User = Depends(get_current_active_user), current_user: User = Depends(get_current_active_user),
): ):
"""Download all flows as a file.""" """Download all flows as a file."""
flows = read_flows(session=session, current_user=current_user) flows = read_flows(current_user=current_user)
return FlowListRead(flows=flows) return FlowListRead(flows=flows)

View file

@ -2,7 +2,7 @@ from sqlmodel import Session
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from langflow.services.getters import get_session from langflow.services.deps import get_session
from langflow.api.v1.schemas import Token from langflow.api.v1.schemas import Token
from langflow.services.auth.utils import ( from langflow.services.auth.utils import (
authenticate_user, authenticate_user,
@ -12,7 +12,7 @@ from langflow.services.auth.utils import (
get_current_active_user, get_current_active_user,
) )
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
router = APIRouter(tags=["Login"]) router = APIRouter(tags=["Login"])
@ -44,9 +44,7 @@ async def login_to_get_access_token(
@router.get("/auto_login") @router.get("/auto_login")
async def auto_login( async def auto_login(db: Session = Depends(get_session), settings_service=Depends(get_settings_service)):
db: Session = Depends(get_session), settings_service=Depends(get_settings_service)
):
if settings_service.auth_settings.AUTO_LOGIN: if settings_service.auth_settings.AUTO_LOGIN:
return create_user_longterm_token(db) return create_user_longterm_token(db)
@ -60,9 +58,7 @@ async def auto_login(
@router.post("/refresh") @router.post("/refresh")
async def refresh_token( async def refresh_token(token: str, current_user: Session = Depends(get_current_active_user)):
token: str, current_user: Session = Depends(get_current_active_user)
):
if token: if token:
return create_refresh_token(token) return create_refresh_token(token)
else: else:

View file

@ -151,9 +151,7 @@ class StreamData(BaseModel):
data: dict data: dict
def __str__(self) -> str: def __str__(self) -> str:
return ( return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
)
class CustomComponentCode(BaseModel): class CustomComponentCode(BaseModel):
@ -200,3 +198,7 @@ class Token(BaseModel):
access_token: str access_token: str
refresh_token: str refresh_token: str
token_type: str token_type: str
class ApiKeyCreateRequest(BaseModel):
api_key: str

View file

@ -0,0 +1,195 @@
import warnings
from typing import Annotated, List, Optional, Union
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query
from langflow.services.auth import utils as auth_utils
from langflow.services.database.models.user.user import User
from langflow.services.deps import get_settings_service, get_store_service
from langflow.services.store.exceptions import CustomException
from langflow.services.store.schema import (
CreateComponentResponse,
DownloadComponentResponse,
ListComponentResponseModel,
StoreComponentCreate,
TagResponse,
UsersLikesResponse,
)
from langflow.services.store.service import StoreService
from langflow.services.store.utils import get_lf_version_from_pypi
router = APIRouter(prefix="/store", tags=["Components Store"])
def get_user_store_api_key(
user: User = Depends(auth_utils.get_current_active_user),
settings_service=Depends(get_settings_service),
):
if not user.store_api_key:
raise HTTPException(status_code=400, detail="You must have a store API key set.")
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
return decrypted
def get_optional_user_store_api_key(
user: User = Depends(auth_utils.get_current_active_user),
settings_service=Depends(get_settings_service),
):
if not user.store_api_key:
return None
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
return decrypted
@router.get("/check/")
def check_if_store_is_enabled(
settings_service=Depends(get_settings_service),
):
return {
"enabled": settings_service.settings.STORE,
}
@router.get("/check/api_key")
async def check_if_store_has_api_key(
api_key: Optional[str] = Depends(get_optional_user_store_api_key),
store_service: StoreService = Depends(get_store_service),
):
if api_key is None:
return {"has_api_key": False, "is_valid": False}
try:
is_valid = await store_service.check_api_key(api_key)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
return {"has_api_key": api_key is not None, "is_valid": is_valid}
@router.post("/components/", response_model=CreateComponentResponse, status_code=201)
async def share_component(
component: StoreComponentCreate,
store_service: StoreService = Depends(get_store_service),
store_api_Key: str = Depends(get_user_store_api_key),
):
try:
# Verify if this is the latest version of Langflow
# If not, raise an error
if not component.last_tested_version:
# Get the local version of Langflow
from langflow import __version__ as current_version
component.last_tested_version = current_version
langflow_version = get_lf_version_from_pypi()
if langflow_version is None:
raise HTTPException(
status_code=500,
detail="Unable to verify the latest version of Langflow",
)
elif langflow_version != component.last_tested_version:
# If the user is using an older version of Langflow, we need to raise an error
# raise ValueError(
warnings.warn(
f"Your version of Langflow ({component.last_tested_version}) is outdated."
f" Please update to the latest version ({langflow_version}) and try again."
)
result = await store_service.upload(store_api_Key, component)
return result
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/components/", response_model=ListComponentResponseModel)
async def get_components(
search: Annotated[Optional[str], Query()] = None,
private: Annotated[Optional[bool], Query()] = None,
is_component: Annotated[Optional[bool], Query()] = None,
tags: Annotated[Optional[list[str]], Query()] = None,
sort: Annotated[Union[list[str], None], Query()] = None,
liked: Annotated[bool, Query()] = False,
filter_by_user: Annotated[bool, Query()] = False,
page: int = 1,
limit: int = 10,
store_service: StoreService = Depends(get_store_service),
store_api_Key: Optional[str] = Depends(get_optional_user_store_api_key),
):
try:
return await store_service.get_list_component_response_model(
search=search,
private=private,
is_component=is_component,
tags=tags,
sort=sort,
liked=liked,
filter_by_user=filter_by_user,
page=page,
limit=limit,
store_api_key=store_api_Key,
)
except CustomException as exc:
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
async def download_component(
component_id: UUID,
store_service: StoreService = Depends(get_store_service),
store_api_Key: str = Depends(get_user_store_api_key),
):
try:
component = await store_service.download(store_api_Key, component_id)
except CustomException as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
if component is None:
raise HTTPException(status_code=400, detail="Component not found")
return component
@router.get("/tags", response_model=List[TagResponse])
async def get_tags(
store_service: StoreService = Depends(get_store_service),
):
try:
return await store_service.get_tags()
except CustomException as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))
@router.get("/users/likes", response_model=List[UsersLikesResponse])
async def get_list_of_components_liked_by_user(
store_service: StoreService = Depends(get_store_service),
store_api_Key: str = Depends(get_user_store_api_key),
):
try:
return await store_service.get_user_likes(store_api_Key)
except CustomException as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))
@router.post("/users/likes/{component_id}", response_model=UsersLikesResponse)
async def like_component(
component_id: UUID,
store_service: StoreService = Depends(get_store_service),
store_api_Key: str = Depends(get_user_store_api_key),
):
try:
result = await store_service.like_component(store_api_Key, str(component_id))
likes_count = await store_service.get_component_likes_count(str(component_id), store_api_Key)
return UsersLikesResponse(likes_count=likes_count, liked_by_user=result)
except CustomException as exc:
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))

View file

@ -13,7 +13,7 @@ from sqlalchemy.exc import IntegrityError
from sqlmodel import Session, select from sqlmodel import Session, select
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from langflow.services.getters import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
from langflow.services.auth.utils import ( from langflow.services.auth.utils import (
get_current_active_superuser, get_current_active_superuser,
get_current_active_user, get_current_active_user,
@ -46,9 +46,7 @@ def add_user(
session.refresh(new_user) session.refresh(new_user)
except IntegrityError as e: except IntegrityError as e:
session.rollback() session.rollback()
raise HTTPException( raise HTTPException(status_code=400, detail="This username is unavailable.") from e
status_code=400, detail="This username is unavailable."
) from e
return new_user return new_user
@ -96,14 +94,10 @@ def patch_user(
Update an existing user's data. Update an existing user's data.
""" """
if not user.is_superuser and user.id != user_id: if not user.is_superuser and user.id != user_id:
raise HTTPException( raise HTTPException(status_code=403, detail="You don't have the permission to update this user")
status_code=403, detail="You don't have the permission to update this user"
)
if user_update.password: if user_update.password:
if not user.is_superuser: if not user.is_superuser:
raise HTTPException( raise HTTPException(status_code=400, detail="You can't change your password here")
status_code=400, detail="You can't change your password here"
)
user_update.password = get_password_hash(user_update.password) user_update.password = get_password_hash(user_update.password)
if user_db := get_user_by_id(session, user_id): if user_db := get_user_by_id(session, user_id):
@ -123,16 +117,12 @@ def reset_password(
Reset a user's password. Reset a user's password.
""" """
if user_id != user.id: if user_id != user.id:
raise HTTPException( raise HTTPException(status_code=400, detail="You can't change another user's password")
status_code=400, detail="You can't change another user's password"
)
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
if verify_password(user_update.password, user.password): if verify_password(user_update.password, user.password):
raise HTTPException( raise HTTPException(status_code=400, detail="You can't use your current password")
status_code=400, detail="You can't use your current password"
)
new_password = get_password_hash(user_update.password) new_password = get_password_hash(user_update.password)
user.password = new_password user.password = new_password
session.commit() session.commit()
@ -151,13 +141,9 @@ def delete_user(
Delete a user from the database. Delete a user from the database.
""" """
if current_user.id == user_id: if current_user.id == user_id:
raise HTTPException( raise HTTPException(status_code=400, detail="You can't delete your own user account")
status_code=400, detail="You can't delete your own user account"
)
elif not current_user.is_superuser: elif not current_user.is_superuser:
raise HTTPException( raise HTTPException(status_code=403, detail="You don't have the permission to delete this user")
status_code=403, detail="You don't have the permission to delete this user"
)
user_db = session.query(User).filter(User.id == user_id).first() user_db = session.query(User).filter(User.id == user_id).first()
if not user_db: if not user_db:

View file

@ -41,9 +41,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
add_new_variables_to_template(input_variables, prompt_request) add_new_variables_to_template(input_variables, prompt_request)
remove_old_variables_from_template( remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request)
old_custom_fields, input_variables, prompt_request
)
update_input_variables_field(input_variables, prompt_request) update_input_variables_field(input_variables, prompt_request)
@ -58,19 +56,12 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
def get_old_custom_fields(prompt_request): def get_old_custom_fields(prompt_request):
try: try:
if ( if len(prompt_request.frontend_node.custom_fields) == 1 and prompt_request.name == "":
len(prompt_request.frontend_node.custom_fields) == 1
and prompt_request.name == ""
):
# If there is only one custom field and the name is empty string # If there is only one custom field and the name is empty string
# then we are dealing with the first prompt request after the node was created # then we are dealing with the first prompt request after the node was created
prompt_request.name = list( prompt_request.name = list(prompt_request.frontend_node.custom_fields.keys())[0]
prompt_request.frontend_node.custom_fields.keys()
)[0]
old_custom_fields = prompt_request.frontend_node.custom_fields[ old_custom_fields = prompt_request.frontend_node.custom_fields[prompt_request.name].copy()
prompt_request.name
].copy()
except KeyError: except KeyError:
old_custom_fields = [] old_custom_fields = []
prompt_request.frontend_node.custom_fields[prompt_request.name] = [] prompt_request.frontend_node.custom_fields[prompt_request.name] = []
@ -92,40 +83,26 @@ def add_new_variables_to_template(input_variables, prompt_request):
) )
if variable in prompt_request.frontend_node.template: if variable in prompt_request.frontend_node.template:
# Set the new field with the old value # Set the new field with the old value
template_field.value = prompt_request.frontend_node.template[variable][ template_field.value = prompt_request.frontend_node.template[variable]["value"]
"value"
]
prompt_request.frontend_node.template[variable] = template_field.to_dict() prompt_request.frontend_node.template[variable] = template_field.to_dict()
# Check if variable is not already in the list before appending # Check if variable is not already in the list before appending
if ( if variable not in prompt_request.frontend_node.custom_fields[prompt_request.name]:
variable prompt_request.frontend_node.custom_fields[prompt_request.name].append(variable)
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
):
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
variable
)
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template( def remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request):
old_custom_fields, input_variables, prompt_request
):
for variable in old_custom_fields: for variable in old_custom_fields:
if variable not in input_variables: if variable not in input_variables:
try: try:
# Remove the variable from custom_fields associated with the given name # Remove the variable from custom_fields associated with the given name
if ( if variable in prompt_request.frontend_node.custom_fields[prompt_request.name]:
variable prompt_request.frontend_node.custom_fields[prompt_request.name].remove(variable)
in prompt_request.frontend_node.custom_fields[prompt_request.name]
):
prompt_request.frontend_node.custom_fields[
prompt_request.name
].remove(variable)
# Remove the variable from the template # Remove the variable from the template
prompt_request.frontend_node.template.pop(variable, None) prompt_request.frontend_node.template.pop(variable, None)
@ -137,6 +114,4 @@ def remove_old_variables_from_template(
def update_input_variables_field(input_variables, prompt_request): def update_input_variables_field(input_variables, prompt_request):
if "input_variables" in prompt_request.frontend_node.template: if "input_variables" in prompt_request.frontend_node.template:
prompt_request.frontend_node.template["input_variables"][ prompt_request.frontend_node.template["input_variables"]["value"] = input_variables
"value"
] = input_variables

View file

@ -72,7 +72,9 @@ class ConversationalAgent(CustomComponent):
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)], extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
) )
agent = OpenAIFunctionsAgent( agent = OpenAIFunctionsAgent(
llm=llm, tools=tools, prompt=prompt # type: ignore llm=llm,
tools=tools,
prompt=prompt, # type: ignore
) )
return AgentExecutor( return AgentExecutor(
agent=agent, agent=agent,

View file

@ -18,9 +18,7 @@ class PromptRunner(CustomComponent):
"code": {"show": False}, "code": {"show": False},
} }
def build( def build(self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}) -> Document:
self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}
) -> Document:
chain = prompt | llm chain = prompt | llm
# The input is an empty dict because the prompt is already filled # The input is an empty dict because the prompt is already filled
result = chain.invoke(input=inputs) result = chain.invoke(input=inputs)

View file

@ -122,9 +122,7 @@ class FileLoaderComponent(CustomComponent):
beta = True beta = True
def build_config(self): def build_config(self):
loader_options = ["Automatic"] + [ loader_options = ["Automatic"] + [loader_info["name"] for loader_info in loaders_info]
loader_info["name"] for loader_info in loaders_info
]
file_types = [] file_types = []
suffixes = [] suffixes = []
@ -214,9 +212,7 @@ class FileLoaderComponent(CustomComponent):
if isinstance(selected_loader_info, dict): if isinstance(selected_loader_info, dict):
loader_import: str = selected_loader_info["import"] loader_import: str = selected_loader_info["import"]
else: else:
raise ValueError( raise ValueError(f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}")
f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}"
)
module_name, class_name = loader_import.rsplit(".", 1) module_name, class_name = loader_import.rsplit(".", 1)
try: try:
@ -224,9 +220,7 @@ class FileLoaderComponent(CustomComponent):
loader_module = __import__(module_name, fromlist=[class_name]) loader_module = __import__(module_name, fromlist=[class_name])
loader_instance = getattr(loader_module, class_name) loader_instance = getattr(loader_module, class_name)
except ImportError as e: except ImportError as e:
raise ValueError( raise ValueError(f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}") from e
f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}"
) from e
result = loader_instance(file_path=file_path) result = loader_instance(file_path=file_path)
return result.load() return result.load()

View file

@ -18,9 +18,7 @@ class MetalRetrieverComponent(CustomComponent):
"code": {"show": False}, "code": {"show": False},
} }
def build( def build(self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None) -> BaseRetriever:
self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None
) -> BaseRetriever:
try: try:
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id) metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
except Exception as e: except Exception as e:

View file

@ -1,18 +1,17 @@
from typing import List, Union from typing import List, Union
from langflow import CustomComponent
from metaphor_python import Metaphor # type: ignore
from langchain.tools import Tool
from langchain.agents import tool from langchain.agents import tool
from langchain.agents.agent_toolkits.base import BaseToolkit from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.tools import Tool
from metaphor_python import Metaphor # type: ignore
from langflow import CustomComponent
class MetaphorToolkit(CustomComponent): class MetaphorToolkit(CustomComponent):
display_name: str = "Metaphor" display_name: str = "Metaphor"
description: str = "Metaphor Toolkit" description: str = "Metaphor Toolkit"
documentation = ( documentation = "https://python.langchain.com/docs/integrations/tools/metaphor_search"
"https://python.langchain.com/docs/integrations/tools/metaphor_search"
)
beta: bool = True beta: bool = True
# api key should be password = True # api key should be password = True
field_config = { field_config = {
@ -33,9 +32,7 @@ class MetaphorToolkit(CustomComponent):
@tool @tool
def search(query: str): def search(query: str):
"""Call search engine with a query.""" """Call search engine with a query."""
return client.search( return client.search(query, use_autoprompt=use_autoprompt, num_results=search_num_results)
query, use_autoprompt=use_autoprompt, num_results=search_num_results
)
@tool @tool
def get_contents(ids: List[str]): def get_contents(ids: List[str]):

View file

@ -30,9 +30,7 @@ class GetRequest(CustomComponent):
}, },
} }
def get_document( def get_document(self, session: requests.Session, url: str, headers: Optional[dict], timeout: int) -> Document:
self, session: requests.Session, url: str, headers: Optional[dict], timeout: int
) -> Document:
try: try:
response = session.get(url, headers=headers, timeout=int(timeout)) response = session.get(url, headers=headers, timeout=int(timeout))
try: try:

View file

@ -11,8 +11,8 @@
# - **Document:** The Document containing the JSON object. # - **Document:** The Document containing the JSON object.
from langflow import CustomComponent
from langchain.schema import Document from langchain.schema import Document
from langflow import CustomComponent
from langflow.services.database.models.base import orjson_dumps from langflow.services.database.models.base import orjson_dumps
@ -20,10 +20,8 @@ class JSONDocumentBuilder(CustomComponent):
display_name: str = "JSON Document Builder" display_name: str = "JSON Document Builder"
description: str = "Build a Document containing a JSON object using a key and another Document page content." description: str = "Build a Document containing a JSON object using a key and another Document page content."
output_types: list[str] = ["Document"] output_types: list[str] = ["Document"]
beta: bool = True beta = True
documentation: str = ( documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder"
"https://docs.langflow.org/components/utilities#json-document-builder"
)
field_config = { field_config = {
"key": {"display_name": "Key"}, "key": {"display_name": "Key"},
@ -38,18 +36,11 @@ class JSONDocumentBuilder(CustomComponent):
documents = None documents = None
if isinstance(document, list): if isinstance(document, list):
documents = [ documents = [
Document( Document(page_content=orjson_dumps({key: doc.page_content}, indent_2=False)) for doc in document
page_content=orjson_dumps({key: doc.page_content}, indent_2=False)
)
for doc in document
] ]
elif isinstance(document, Document): elif isinstance(document, Document):
documents = Document( documents = Document(page_content=orjson_dumps({key: document.page_content}, indent_2=False))
page_content=orjson_dumps({key: document.page_content}, indent_2=False)
)
else: else:
raise TypeError( raise TypeError(f"Expected Document or list of Documents, got {type(document)}")
f"Expected Document or list of Documents, got {type(document)}"
)
self.repr_value = documents self.repr_value = documents
return documents return documents

View file

@ -65,16 +65,12 @@ class PostRequest(CustomComponent):
if not isinstance(document, list) and isinstance(document, Document): if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document] documents: list[Document] = [document]
elif isinstance(document, list) and all( elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document):
isinstance(doc, Document) for doc in document
):
documents = document documents = document
else: else:
raise ValueError("document must be a Document or a list of Documents") raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session: with requests.Session() as session:
documents = [ documents = [self.post_document(session, doc, url, headers) for doc in documents]
self.post_document(session, doc, url, headers) for doc in documents
]
self.repr_value = documents self.repr_value = documents
return documents return documents

View file

@ -39,9 +39,7 @@ class UpdateRequest(CustomComponent):
) -> Document: ) -> Document:
try: try:
if method == "PATCH": if method == "PATCH":
response = session.patch( response = session.patch(url, headers=headers, data=document.page_content)
url, headers=headers, data=document.page_content
)
elif method == "PUT": elif method == "PUT":
response = session.put(url, headers=headers, data=document.page_content) response = session.put(url, headers=headers, data=document.page_content)
else: else:
@ -78,17 +76,12 @@ class UpdateRequest(CustomComponent):
if not isinstance(document, list) and isinstance(document, Document): if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document] documents: list[Document] = [document]
elif isinstance(document, list) and all( elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document):
isinstance(doc, Document) for doc in document
):
documents = document documents = document
else: else:
raise ValueError("document must be a Document or a list of Documents") raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session: with requests.Session() as session:
documents = [ documents = [self.update_document(session, doc, url, headers, method) for doc in documents]
self.update_document(session, doc, url, headers, method)
for doc in documents
]
self.repr_value = documents self.repr_value = documents
return documents return documents

View file

@ -86,8 +86,7 @@ class ChromaComponent(CustomComponent):
if chroma_server_host is not None: if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings( chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
or None,
chroma_server_host=chroma_server_host, chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None, chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None, chroma_server_grpc_port=chroma_server_grpc_port or None,
@ -104,6 +103,4 @@ class ChromaComponent(CustomComponent):
client_settings=chroma_settings, client_settings=chroma_settings,
) )
return Chroma( return Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
persist_directory=persist_directory, client_settings=chroma_settings
)

View file

@ -1,19 +1,17 @@
from typing import Optional, Union from typing import Optional, Union
from langflow import CustomComponent
from langchain.schema import BaseRetriever, Document
from langchain.vectorstores import Vectara from langchain.vectorstores import Vectara
from langchain.schema import Document
from langchain.vectorstores.base import VectorStore from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseRetriever
from langflow import CustomComponent
class VectaraComponent(CustomComponent): class VectaraComponent(CustomComponent):
display_name: str = "Vectara" display_name: str = "Vectara"
description: str = "Implementation of Vector Store using Vectara" description: str = "Implementation of Vector Store using Vectara"
documentation = ( documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
"https://python.langchain.com/docs/integrations/vectorstores/vectara" beta = True
)
beta: bool = True
# api key should be password = True # api key should be password = True
field_config = { field_config = {
"vectara_customer_id": {"display_name": "Vectara Customer ID"}, "vectara_customer_id": {"display_name": "Vectara Customer ID"},

View file

@ -14,9 +14,7 @@ class PostgresqlVectorComponent(CustomComponent):
display_name: str = "PGVector" display_name: str = "PGVector"
description: str = "Implementation of Vector Store using PostgreSQL" description: str = "Implementation of Vector Store using PostgreSQL"
documentation = ( documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
)
beta = True beta = True
def build_config(self): def build_config(self):

View file

@ -1,40 +1,25 @@
# LANGCHAIN_BASE_TYPES = {
# "Chain": Chain,
# "AgentExecutor": AgentExecutor,
# "Tool": Tool,
# "BaseLLM": BaseLLM,
# "PromptTemplate": PromptTemplate,
# "BaseLoader": BaseLoader,
# "Document": Document,
# "TextSplitter": TextSplitter,
# "VectorStore": VectorStore,
# "Embeddings": Embeddings,
# "BaseRetriever": BaseRetriever,
# "BaseOutputParser": BaseOutputParser,
# "BaseMemory": BaseMemory,
# "BaseChatMemory": BaseChatMemory,
# }
from .constants import ( from .constants import (
Tool, AgentExecutor,
PromptTemplate,
ChatPromptTemplate,
BasePromptTemplate,
Chain,
BaseChatMemory, BaseChatMemory,
BaseLanguageModel,
BaseLLM, BaseLLM,
BaseLoader, BaseLoader,
BaseMemory, BaseMemory,
BaseOutputParser, BaseOutputParser,
BasePromptTemplate,
BaseRetriever, BaseRetriever,
VectorStore,
Embeddings,
TextSplitter,
Document,
AgentExecutor,
NestedDict,
Data,
BaseLanguageModel,
Callable, Callable,
Chain,
ChatPromptTemplate,
Data,
Document,
Embeddings,
NestedDict,
Object,
PromptTemplate,
TextSplitter,
Tool,
VectorStore,
) )
__all__ = [ __all__ = [
@ -55,6 +40,7 @@ __all__ = [
"TextSplitter", "TextSplitter",
"Document", "Document",
"AgentExecutor", "AgentExecutor",
"Object",
"Callable", "Callable",
"BasePromptTemplate", "BasePromptTemplate",
"ChatPromptTemplate", "ChatPromptTemplate",

View file

@ -1,21 +1,26 @@
from typing import Callable, Dict, Union
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader from langchain.document_loaders.base import BaseLoader
from langchain.llms.base import BaseLLM, BaseLanguageModel from langchain.llms.base import BaseLanguageModel, BaseLLM
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.prompts import PromptTemplate, ChatPromptTemplate, BasePromptTemplate from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
from langchain.schema import BaseOutputParser, BaseRetriever, Document from langchain.schema import BaseOutputParser, BaseRetriever, Document
from langchain.schema.embeddings import Embeddings from langchain.schema.embeddings import Embeddings
from langchain.schema.memory import BaseMemory from langchain.schema.memory import BaseMemory
from langchain.text_splitter import TextSplitter from langchain.text_splitter import TextSplitter
from langchain.tools import Tool from langchain.tools import Tool
from langchain.vectorstores.base import VectorStore from langchain.vectorstores.base import VectorStore
from typing import Union, Dict, Callable
# Type alias for more complex dicts # Type alias for more complex dicts
NestedDict = Dict[str, Union[str, Dict]] NestedDict = Dict[str, Union[str, Dict]]
class Object:
pass
class Data: class Data:
pass pass
@ -50,5 +55,6 @@ CUSTOM_COMPONENT_SUPPORTED_TYPES = {
"dict": dict, "dict": dict,
"NestedDict": NestedDict, "NestedDict": NestedDict,
"Data": Data, "Data": Data,
"Object": Object,
"Callable": Callable, "Callable": Callable,
} }

View file

@ -8,9 +8,7 @@ if TYPE_CHECKING:
class SourceHandle(BaseModel): class SourceHandle(BaseModel):
baseClasses: List[str] = Field( baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
..., description="List of base classes for the source handle."
)
dataType: str = Field(..., description="Data type for the source handle.") dataType: str = Field(..., description="Data type for the source handle.")
id: str = Field(..., description="Unique identifier for the source handle.") id: str = Field(..., description="Unique identifier for the source handle.")
@ -18,9 +16,7 @@ class SourceHandle(BaseModel):
class TargetHandle(BaseModel): class TargetHandle(BaseModel):
fieldName: str = Field(..., description="Field name for the target handle.") fieldName: str = Field(..., description="Field name for the target handle.")
id: str = Field(..., description="Unique identifier for the target handle.") id: str = Field(..., description="Unique identifier for the target handle.")
inputTypes: Optional[List[str]] = Field( inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
None, description="List of input types for the target handle."
)
type: str = Field(..., description="Type of the target handle.") type: str = Field(..., description="Type of the target handle.")
@ -49,23 +45,17 @@ class Edge:
def validate_handles(self) -> None: def validate_handles(self) -> None:
if self.target_handle.inputTypes is None: if self.target_handle.inputTypes is None:
self.valid_handles = ( self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
self.target_handle.type in self.source_handle.baseClasses
)
else: else:
self.valid_handles = ( self.valid_handles = (
any( any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
baseClass in self.target_handle.inputTypes
for baseClass in self.source_handle.baseClasses
)
or self.target_handle.type in self.source_handle.baseClasses or self.target_handle.type in self.source_handle.baseClasses
) )
if not self.valid_handles: if not self.valid_handles:
logger.debug(self.source_handle) logger.debug(self.source_handle)
logger.debug(self.target_handle) logger.debug(self.target_handle)
raise ValueError( raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has invalid handles"
f"has invalid handles"
) )
def __setstate__(self, state): def __setstate__(self, state):
@ -87,11 +77,7 @@ class Edge:
# Both lists contain strings and sometimes a string contains the value we are # Both lists contain strings and sometimes a string contains the value we are
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"] # looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
# so we need to check if any of the strings in source_types is in target_reqs # so we need to check if any of the strings in source_types is in target_reqs
self.valid = any( self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
output in target_req
for output in self.source_types
for target_req in self.target_reqs
)
# Get what type of input the target node is expecting # Get what type of input the target node is expecting
self.matched_type = next( self.matched_type = next(
@ -103,8 +89,7 @@ class Edge:
logger.debug(self.source_types) logger.debug(self.source_types)
logger.debug(self.target_reqs) logger.debug(self.target_reqs)
raise ValueError( raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has no matched type"
f"has no matched type"
) )
def __repr__(self) -> str: def __repr__(self) -> str:
@ -117,8 +102,4 @@ class Edge:
return hash(self.__repr__()) return hash(self.__repr__())
def __eq__(self, __value: object) -> bool: def __eq__(self, __value: object) -> bool:
return ( return self.__repr__() == __value.__repr__() if isinstance(__value, Edge) else False
self.__repr__() == __value.__repr__()
if isinstance(__value, Edge)
else False
)

View file

@ -104,9 +104,7 @@ class Graph:
return return
for node in self.nodes: for node in self.nodes:
if not self._validate_node(node): if not self._validate_node(node):
raise ValueError( raise ValueError(f"{node.vertex_type} is not connected to any other components")
f"{node.vertex_type} is not connected to any other components"
)
def _validate_node(self, node: Vertex) -> bool: def _validate_node(self, node: Vertex) -> bool:
"""Validates a node.""" """Validates a node."""
@ -119,9 +117,7 @@ class Graph:
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]: def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
"""Returns the nodes connected to a node.""" """Returns the nodes connected to a node."""
connected_nodes: List[Vertex] = [ connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node]
edge.source for edge in self.edges if edge.target == node
]
return connected_nodes return connected_nodes
def build(self) -> Chain: def build(self) -> Chain:
@ -149,9 +145,7 @@ class Graph:
def dfs(node): def dfs(node):
if state[node] == 1: if state[node] == 1:
# We have a cycle # We have a cycle
raise ValueError( raise ValueError("Graph contains a cycle, cannot perform topological sort")
"Graph contains a cycle, cannot perform topological sort"
)
if state[node] == 0: if state[node] == 0:
state[node] = 1 state[node] = 1
for edge in node.edges: for edge in node.edges:
@ -245,7 +239,5 @@ class Graph:
def __repr__(self): def __repr__(self):
node_ids = [node.id for node in self.nodes] node_ids = [node.id for node in self.nodes]
edges_repr = "\n".join( edges_repr = "\n".join([f"{edge.source.id} --> {edge.target.id}" for edge in self.edges])
[f"{edge.source.id} --> {edge.target.id}" for edge in self.edges]
)
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}" return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"

View file

@ -47,10 +47,7 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()}, **{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
**{ **{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
t: types.CustomComponentVertex
for t in custom_component_creator.to_list()
},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()}, **{t: types.RetrieverVertex for t in retriever_creator.to_list()},
} }

View file

@ -28,23 +28,14 @@ def ungroup_node(group_node_data, base_flow):
g_edges = flow["data"]["edges"] g_edges = flow["data"]["edges"]
# Redirect edges to the correct proxy node # Redirect edges to the correct proxy node
updated_edges = get_updated_edges( updated_edges = get_updated_edges(base_flow, g_nodes, g_edges, group_node_data["id"])
base_flow, g_nodes, g_edges, group_node_data["id"]
)
# Update template values # Update template values
update_template(template, g_nodes) update_template(template, g_nodes)
nodes = [ nodes = [n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]] + g_nodes
n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]
] + g_nodes
edges = ( edges = (
[ [e for e in base_flow["edges"] if e["target"] != group_node_data["id"] and e["source"] != group_node_data["id"]]
e
for e in base_flow["edges"]
if e["target"] != group_node_data["id"]
and e["source"] != group_node_data["id"]
]
+ g_edges + g_edges
+ updated_edges + updated_edges
) )
@ -66,11 +57,7 @@ def process_flow(flow_object):
if node_id in processed_nodes: if node_id in processed_nodes:
return return
if ( if node.get("data") and node["data"].get("node") and node["data"]["node"].get("flow"):
node.get("data")
and node["data"].get("node")
and node["data"]["node"].get("flow")
):
process_flow(node["data"]["node"]["flow"]["data"]) process_flow(node["data"]["node"]["flow"]["data"])
new_nodes = ungroup_node(node["data"], cloned_flow) new_nodes = ungroup_node(node["data"], cloned_flow)
# Add new nodes to the queue for future processing # Add new nodes to the queue for future processing
@ -108,26 +95,16 @@ def update_template(template, g_nodes):
if node_index != -1: if node_index != -1:
display_name = None display_name = None
show = g_nodes[node_index]["data"]["node"]["template"][field]["show"] show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
advanced = g_nodes[node_index]["data"]["node"]["template"][field][ advanced = g_nodes[node_index]["data"]["node"]["template"][field]["advanced"]
"advanced"
]
if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]: if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
display_name = g_nodes[node_index]["data"]["node"]["template"][field][ display_name = g_nodes[node_index]["data"]["node"]["template"][field]["display_name"]
"display_name"
]
else: else:
display_name = g_nodes[node_index]["data"]["node"]["template"][field][ display_name = g_nodes[node_index]["data"]["node"]["template"][field]["name"]
"name"
]
g_nodes[node_index]["data"]["node"]["template"][field] = value g_nodes[node_index]["data"]["node"]["template"][field] = value
g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
g_nodes[node_index]["data"]["node"]["template"][field][ g_nodes[node_index]["data"]["node"]["template"][field]["advanced"] = advanced
"advanced" g_nodes[node_index]["data"]["node"]["template"][field]["display_name"] = display_name
] = advanced
g_nodes[node_index]["data"]["node"]["template"][field][
"display_name"
] = display_name
def update_target_handle(new_edge, g_nodes, group_node_id): def update_target_handle(new_edge, g_nodes, group_node_id):

View file

@ -51,9 +51,7 @@ class Vertex:
self.params.pop(target_param, None) self.params.pop(target_param, None)
continue continue
if target_param in self.params and not is_basic_type( if target_param in self.params and not is_basic_type(self.params[target_param]):
self.params[target_param]
):
# edge.source.params = {} # edge.source.params = {}
edge.source._build_params() edge.source._build_params()
edge.source._built_object = UnbuiltObject() edge.source._built_object = UnbuiltObject()
@ -99,29 +97,17 @@ class Vertex:
def _parse_data(self) -> None: def _parse_data(self) -> None:
self.data = self._data["data"] self.data = self._data["data"]
self.output = self.data["node"]["base_classes"] self.output = self.data["node"]["base_classes"]
template_dicts = { template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
self.required_inputs = [ self.required_inputs = [
template_dicts[key]["type"] template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
for key, value in template_dicts.items()
if value["required"]
] ]
self.optional_inputs = [ self.optional_inputs = [
template_dicts[key]["type"] template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
for key, value in template_dicts.items()
if not value["required"]
] ]
# Add the template_dicts[key]["input_types"] to the optional_inputs # Add the template_dicts[key]["input_types"] to the optional_inputs
self.optional_inputs.extend( self.optional_inputs.extend(
[ [input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
input_type
for value in template_dicts.values()
for input_type in value.get("input_types", [])
]
) )
template_dict = self.data["node"]["template"] template_dict = self.data["node"]["template"]
@ -160,11 +146,7 @@ class Vertex:
# and use that as the value for the param # and use that as the value for the param
# If the type is "str", then we need to get the value of the "value" key # If the type is "str", then we need to get the value of the "value" key
# and use that as the value for the param # and use that as the value for the param
template_dict = { template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
params = self.params.copy() if self.params else {} params = self.params.copy() if self.params else {}
for edge in self.edges: for edge in self.edges:
@ -209,11 +191,7 @@ class Vertex:
# before passing it to the build method # before passing it to the build method
_value = value.get("value") _value = value.get("value")
if isinstance(_value, list): if isinstance(_value, list):
params[key] = { params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
k: v
for item in value.get("value", [])
for k, v in item.items()
}
elif isinstance(_value, dict): elif isinstance(_value, dict):
params[key] = _value params[key] = _value
elif value.get("type") == "int" and value.get("value") is not None: elif value.get("type") == "int" and value.get("value") is not None:
@ -304,9 +282,7 @@ class Vertex:
self._extend_params_list_with_result(key, result) self._extend_params_list_with_result(key, result)
self.params[key] = result self.params[key] = result
def _build_list_of_nodes_and_update_params( def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
self, key, nodes: List["Vertex"], user_id=None
):
""" """
Iterates over a list of nodes, builds each and updates the params dictionary. Iterates over a list of nodes, builds each and updates the params dictionary.
""" """
@ -358,9 +334,7 @@ class Vertex:
self._update_built_object_and_artifacts(result) self._update_built_object_and_artifacts(result)
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
raise ValueError( raise ValueError(f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}") from exc
f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}"
) from exc
def _update_built_object_and_artifacts(self, result): def _update_built_object_and_artifacts(self, result):
""" """
@ -408,8 +382,4 @@ class Vertex:
def _built_object_repr(self): def _built_object_repr(self):
# Add a message with an emoji, stars for sucess, # Add a message with an emoji, stars for sucess,
return ( return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵‍💫"
"Built sucessfully ✨"
if self._built_object is not None
else "Failed to build 😵‍💫"
)

View file

@ -107,11 +107,9 @@ class DocumentLoaderVertex(Vertex):
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
avg_length = sum( avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len(
len(doc.page_content) self._built_object
for doc in self._built_object )
if hasattr(doc, "page_content")
) / len(self._built_object)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {int(avg_length)} \nAvg. Document Length (characters): {int(avg_length)}
Documents: {self._built_object[:3]}...""" Documents: {self._built_object[:3]}..."""
@ -184,9 +182,7 @@ class TextSplitterVertex(Vertex):
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len( avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {int(avg_length)} \nAvg. Document Length (characters): {int(avg_length)}
\nDocuments: {self._built_object[:3]}...""" \nDocuments: {self._built_object[:3]}..."""
@ -205,6 +201,8 @@ class ChainVertex(Vertex):
**kwargs, **kwargs,
) -> Any: ) -> Any:
if not self._built or force: if not self._built or force:
# Temporarily remove the code from the params
self.params.pop("code", None)
# Check if the chain requires a PromptVertex # Check if the chain requires a PromptVertex
# Temporarily remove "code" from the params # Temporarily remove "code" from the params
@ -234,27 +232,18 @@ class PromptVertex(Vertex):
**kwargs, **kwargs,
) -> Any: ) -> Any:
if not self._built or force: if not self._built or force:
if ( if "input_variables" not in self.params or self.params["input_variables"] is None:
"input_variables" not in self.params
or self.params["input_variables"] is None
):
self.params["input_variables"] = [] self.params["input_variables"] = []
# Check if it is a ZeroShotPrompt and needs a tool # Check if it is a ZeroShotPrompt and needs a tool
if "ShotPrompt" in self.vertex_type: if "ShotPrompt" in self.vertex_type:
tools = ( tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
[tool_node.build(user_id=user_id) for tool_node in tools]
if tools is not None
else []
)
# flatten the list of tools if it is a list of lists # flatten the list of tools if it is a list of lists
# first check if it is a list # first check if it is a list
if tools and isinstance(tools, list) and isinstance(tools[0], list): if tools and isinstance(tools, list) and isinstance(tools[0], list):
tools = flatten_list(tools) tools = flatten_list(tools)
self.params["tools"] = tools self.params["tools"] = tools
prompt_params = [ prompt_params = [
key key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
for key, value in self.params.items()
if isinstance(value, str) and key != "format_instructions"
] ]
else: else:
prompt_params = ["template"] prompt_params = ["template"]
@ -264,9 +253,7 @@ class PromptVertex(Vertex):
prompt_text = self.params[param] prompt_text = self.params[param]
variables = extract_input_variables_from_prompt(prompt_text) variables = extract_input_variables_from_prompt(prompt_text)
self.params["input_variables"].extend(variables) self.params["input_variables"].extend(variables)
self.params["input_variables"] = list( self.params["input_variables"] = list(set(self.params["input_variables"]))
set(self.params["input_variables"])
)
elif isinstance(self.params, dict): elif isinstance(self.params, dict):
self.params.pop("input_variables", None) self.params.pop("input_variables", None)
@ -274,11 +261,7 @@ class PromptVertex(Vertex):
return self._built_object return self._built_object
def _built_object_repr(self): def _built_object_repr(self):
if ( if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
not self.artifacts
or self._built_object is None
or not hasattr(self._built_object, "format")
):
return super()._built_object_repr() return super()._built_object_repr()
# We'll build the prompt with the artifacts # We'll build the prompt with the artifacts
# to show the user what the prompt looks like # to show the user what the prompt looks like
@ -288,9 +271,7 @@ class PromptVertex(Vertex):
# so the prompt format doesn't break # so the prompt format doesn't break
artifacts.pop("handle_keys", None) artifacts.pop("handle_keys", None)
try: try:
if not hasattr(self._built_object, "template") and hasattr( if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
self._built_object, "prompt"
):
template = self._built_object.prompt.template template = self._built_object.prompt.template
else: else:
template = self._built_object.template template = self._built_object.template
@ -298,11 +279,7 @@ class PromptVertex(Vertex):
if value: if value:
replace_key = "{" + key + "}" replace_key = "{" + key + "}"
template = template.replace(replace_key, value) template = template.replace(replace_key, value)
return ( return template if isinstance(template, str) else f"{self.vertex_type}({template})"
template
if isinstance(template, str)
else f"{self.vertex_type}({template})"
)
except KeyError: except KeyError:
return str(self._built_object) return str(self._built_object)

View file

@ -5,7 +5,7 @@ from langchain.agents import types
from langflow.custom.customs import get_custom_nodes from langflow.custom.customs import get_custom_nodes
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.agents import AgentFrontendNode from langflow.template.frontend_node.agents import AgentFrontendNode
from loguru import logger from loguru import logger
@ -42,9 +42,7 @@ class AgentCreator(LangChainTypeCreator):
add_function=True, add_function=True,
method_name=self.from_method_nodes[name], method_name=self.from_method_nodes[name],
) )
return build_template_from_class( return build_template_from_class(name, self.type_to_loader_dict, add_function=True)
name, self.type_to_loader_dict, add_function=True
)
except ValueError as exc: except ValueError as exc:
raise ValueError("Agent not found") from exc raise ValueError("Agent not found") from exc
except AttributeError as exc: except AttributeError as exc:
@ -56,15 +54,8 @@ class AgentCreator(LangChainTypeCreator):
names = [] names = []
settings_service = get_settings_service() settings_service = get_settings_service()
for _, agent in self.type_to_loader_dict.items(): for _, agent in self.type_to_loader_dict.items():
agent_name = ( agent_name = agent.function_name() if hasattr(agent, "function_name") else agent.__name__
agent.function_name() if agent_name in settings_service.settings.AGENTS or settings_service.settings.DEV:
if hasattr(agent, "function_name")
else agent.__name__
)
if (
agent_name in settings_service.settings.AGENTS
or settings_service.settings.DEV
):
names.append(agent_name) names.append(agent_name)
return names return names

View file

@ -68,7 +68,8 @@ class JsonAgent(CustomAgentExecutor):
prompt=prompt, prompt=prompt,
) )
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, allowed_tools=tool_names # type: ignore llm_chain=llm_chain,
allowed_tools=tool_names, # type: ignore
) )
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True) return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
@ -92,11 +93,7 @@ class CSVAgent(CustomAgentExecutor):
@classmethod @classmethod
def from_toolkit_and_llm( def from_toolkit_and_llm(
cls, cls, path: str, llm: BaseLanguageModel, pandas_kwargs: Optional[dict] = None, **kwargs: Any
path: str,
llm: BaseLanguageModel,
pandas_kwargs: Optional[dict] = None,
**kwargs: Any
): ):
import pandas as pd # type: ignore import pandas as pd # type: ignore
@ -117,7 +114,9 @@ class CSVAgent(CustomAgentExecutor):
) )
tool_names = {tool.name for tool in tools} tool_names = {tool.name for tool in tools}
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain,
allowed_tools=tool_names,
**kwargs, # type: ignore
) )
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True) return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
@ -141,9 +140,7 @@ class VectorStoreAgent(CustomAgentExecutor):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@classmethod @classmethod
def from_toolkit_and_llm( def from_toolkit_and_llm(cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any):
cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any
):
"""Construct a vectorstore agent from an LLM and tools.""" """Construct a vectorstore agent from an LLM and tools."""
toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm) toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm)
@ -156,11 +153,11 @@ class VectorStoreAgent(CustomAgentExecutor):
) )
tool_names = {tool.name for tool in tools} tool_names = {tool.name for tool in tools}
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain,
) allowed_tools=tool_names,
return AgentExecutor.from_agent_and_tools( **kwargs, # type: ignore
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
) )
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
@ -181,9 +178,7 @@ class SQLAgent(CustomAgentExecutor):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@classmethod @classmethod
def from_toolkit_and_llm( def from_toolkit_and_llm(cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any):
cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any
):
"""Construct an SQL agent from an LLM and tools.""" """Construct an SQL agent from an LLM and tools."""
db = SQLDatabase.from_uri(database_uri) db = SQLDatabase.from_uri(database_uri)
toolkit = SQLDatabaseToolkit(db=db, llm=llm) toolkit = SQLDatabaseToolkit(db=db, llm=llm)
@ -201,9 +196,7 @@ class SQLAgent(CustomAgentExecutor):
llmchain = LLMChain( llmchain = LLMChain(
llm=llm, llm=llm,
prompt=PromptTemplate( prompt=PromptTemplate(template=QUERY_CHECKER, input_variables=["query", "dialect"]),
template=QUERY_CHECKER, input_variables=["query", "dialect"]
),
) )
tools = [ tools = [
@ -226,7 +219,9 @@ class SQLAgent(CustomAgentExecutor):
) )
tool_names = {tool.name for tool in tools} # type: ignore tool_names = {tool.name for tool in tools} # type: ignore
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain,
allowed_tools=tool_names,
**kwargs, # type: ignore
) )
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(
agent=agent, agent=agent,
@ -257,10 +252,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
@classmethod @classmethod
def from_toolkit_and_llm( def from_toolkit_and_llm(
cls, cls, llm: BaseLanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit, **kwargs: Any
llm: BaseLanguageModel,
vectorstoreroutertoolkit: VectorStoreRouterToolkit,
**kwargs: Any
): ):
"""Construct a vector store router agent from an LLM and tools.""" """Construct a vector store router agent from an LLM and tools."""
@ -276,11 +268,11 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
) )
tool_names = {tool.name for tool in tools} tool_names = {tool.name for tool in tools}
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore llm_chain=llm_chain,
) allowed_tools=tool_names,
return AgentExecutor.from_agent_and_tools( **kwargs, # type: ignore
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
) )
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)

View file

@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Type, Union from typing import Any, Dict, List, Optional, Type, Union
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.agents import AgentExecutor from langchain.agents import AgentExecutor
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from pydantic import BaseModel from pydantic import BaseModel
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
@ -30,13 +30,8 @@ class LangChainTypeCreator(BaseModel, ABC):
settings_service = get_settings_service() settings_service = get_settings_service()
if self.name_docs_dict is None: if self.name_docs_dict is None:
try: try:
type_settings = getattr( type_settings = getattr(settings_service.settings, self.type_name.upper())
settings_service.settings, self.type_name.upper() self.name_docs_dict = {name: value_dict["documentation"] for name, value_dict in type_settings.items()}
)
self.name_docs_dict = {
name: value_dict["documentation"]
for name, value_dict in type_settings.items()
}
except AttributeError as exc: except AttributeError as exc:
logger.error(f"Error getting settings for {self.type_name}: {exc}") logger.error(f"Error getting settings for {self.type_name}: {exc}")

View file

@ -3,7 +3,7 @@ from typing import Any, ClassVar, Dict, List, Optional, Type
from langflow.custom.customs import get_custom_nodes from langflow.custom.customs import get_custom_nodes
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.chains import ChainFrontendNode from langflow.template.frontend_node.chains import ChainFrontendNode
from loguru import logger from loguru import logger
@ -33,8 +33,7 @@ class ChainCreator(LangChainTypeCreator):
if self.type_dict is None: if self.type_dict is None:
settings_service = get_settings_service() settings_service = get_settings_service()
self.type_dict: dict[str, Any] = { self.type_dict: dict[str, Any] = {
chain_name: import_class(f"langchain.chains.{chain_name}") chain_name: import_class(f"langchain.chains.{chain_name}") for chain_name in chains.__all__
for chain_name in chains.__all__
} }
from langflow.interface.chains.custom import CUSTOM_CHAINS from langflow.interface.chains.custom import CUSTOM_CHAINS
@ -45,8 +44,7 @@ class ChainCreator(LangChainTypeCreator):
self.type_dict = { self.type_dict = {
name: chain name: chain
for name, chain in self.type_dict.items() for name, chain in self.type_dict.items()
if name in settings_service.settings.CHAINS if name in settings_service.settings.CHAINS or settings_service.settings.DEV
or settings_service.settings.DEV
} }
return self.type_dict return self.type_dict
@ -61,9 +59,7 @@ class ChainCreator(LangChainTypeCreator):
method_name=self.from_method_nodes[name], method_name=self.from_method_nodes[name],
add_function=True, add_function=True,
) )
return build_template_from_class( return build_template_from_class(name, self.type_to_loader_dict, add_function=True)
name, self.type_to_loader_dict, add_function=True
)
except ValueError as exc: except ValueError as exc:
raise ValueError(f"Chain {name} not found: {exc}") from exc raise ValueError(f"Chain {name} not found: {exc}") from exc
except AttributeError as exc: except AttributeError as exc:
@ -73,11 +69,7 @@ class ChainCreator(LangChainTypeCreator):
def to_list(self) -> List[str]: def to_list(self) -> List[str]:
names = [] names = []
for _, chain in self.type_to_loader_dict.items(): for _, chain in self.type_to_loader_dict.items():
chain_name = ( chain_name = chain.function_name() if hasattr(chain, "function_name") else chain.__name__
chain.function_name()
if hasattr(chain, "function_name")
else chain.__name__
)
names.append(chain_name) names.append(chain_name)
return names return names

View file

@ -41,9 +41,7 @@ class BaseCustomConversationChain(ConversationChain):
values["template"] = values["template"].format(**format_dict) values["template"] = values["template"].format(**format_dict)
values["template"] = values["template"] values["template"] = values["template"]
values["input_variables"] = extract_input_variables_from_prompt( values["input_variables"] = extract_input_variables_from_prompt(values["template"])
values["template"]
)
values["prompt"].template = values["template"] values["prompt"].template = values["template"]
values["prompt"].input_variables = values["input_variables"] values["prompt"].input_variables = values["input_variables"]
return values return values
@ -54,9 +52,7 @@ class SeriesCharacterChain(BaseCustomConversationChain):
character: str character: str
series: str series: str
template: Optional[ template: Optional[str] = """I want you to act like {character} from {series}.
str
] = """I want you to act like {character} from {series}.
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
You must know all of the knowledge of {character}. You must know all of the knowledge of {character}.
Current conversation: Current conversation:
@ -71,9 +67,7 @@ Human: {input}
class MidJourneyPromptChain(BaseCustomConversationChain): class MidJourneyPromptChain(BaseCustomConversationChain):
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts.""" """MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
template: Optional[ template: Optional[str] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
str
] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI. Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible. Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures. For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
@ -87,9 +81,7 @@ class MidJourneyPromptChain(BaseCustomConversationChain):
class TimeTravelGuideChain(BaseCustomConversationChain): class TimeTravelGuideChain(BaseCustomConversationChain):
template: Optional[ template: Optional[str] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
str
] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
Current conversation: Current conversation:
{history} {history}
Human: {input} Human: {input}

View file

@ -1,8 +1,8 @@
import ast import ast
import inspect import inspect
import traceback import traceback
from typing import Any, Dict, List, Type, Union
from typing import Dict, Any, List, Type, Union
from fastapi import HTTPException from fastapi import HTTPException
from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails
@ -104,7 +104,7 @@ class CodeParser:
func.args = self.parse_function_args(node) func.args = self.parse_function_args(node)
func.body = self.parse_function_body(node) func.body = self.parse_function_body(node)
return func.dict() return func.model_dump()
def parse_function_args(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: def parse_function_args(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
""" """
@ -127,22 +127,14 @@ class CodeParser:
num_defaults = len(node.args.defaults) num_defaults = len(node.args.defaults)
num_missing_defaults = num_args - num_defaults num_missing_defaults = num_args - num_defaults
missing_defaults = [None] * num_missing_defaults missing_defaults = [None] * num_missing_defaults
default_values = [ default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
ast.unparse(default).strip("'") if default else None
for default in node.args.defaults
]
# Now check all default values to see if there # Now check all default values to see if there
# are any "None" values in the middle # are any "None" values in the middle
default_values = [ default_values = [None if value == "None" else value for value in default_values]
None if value == "None" else value for value in default_values
]
defaults = missing_defaults + default_values defaults = missing_defaults + default_values
args = [ args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)]
self.parse_arg(arg, default)
for arg, default in zip(node.args.args, defaults)
]
return args return args
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
@ -160,17 +152,11 @@ class CodeParser:
""" """
Parses the keyword-only arguments of a function or method node. Parses the keyword-only arguments of a function or method node.
""" """
kw_defaults = [None] * ( kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
len(node.args.kwonlyargs) - len(node.args.kw_defaults) ast.unparse(default) if default else None for default in node.args.kw_defaults
) + [
ast.unparse(default) if default else None
for default in node.args.kw_defaults
] ]
args = [ args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)]
self.parse_arg(arg, default)
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
]
return args return args
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
@ -247,16 +233,14 @@ class CodeParser:
else: else:
class_details.methods.append(method) class_details.methods.append(method)
self.data["classes"].append(class_details.dict()) self.data["classes"].append(class_details.model_dump())
def parse_global_vars(self, node: ast.Assign) -> None: def parse_global_vars(self, node: ast.Assign) -> None:
""" """
Extracts global variables from the code. Extracts global variables from the code.
""" """
global_var = { global_var = {
"targets": [ "targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
],
"value": ast.unparse(node.value), "value": ast.unparse(node.value),
} }
self.data["global_vars"].append(global_var) self.data["global_vars"].append(global_var)

View file

@ -1,9 +1,10 @@
import ast import ast
from typing import Any, ClassVar, Optional from typing import Any, ClassVar, Optional
from fastapi import HTTPException from fastapi import HTTPException
from langflow.utils import validate
from langflow.interface.custom.code_parser import CodeParser from langflow.interface.custom.code_parser import CodeParser
from langflow.utils import validate
class ComponentCodeNullError(HTTPException): class ComponentCodeNullError(HTTPException):
@ -16,9 +17,7 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
class Component: class Component:
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided." ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[ ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
str
] = "The name of the entrypoint function must be provided."
code: Optional[str] = None code: Optional[str] = None
_function_entrypoint_name: str = "build" _function_entrypoint_name: str = "build"

View file

@ -17,6 +17,7 @@ from langflow.field_typing import (
AgentExecutor, AgentExecutor,
NestedDict, NestedDict,
Data, Data,
Object,
) )

View file

@ -1,17 +1,16 @@
from typing import Any, Callable, ClassVar, List, Optional, Union from typing import Any, Callable, ClassVar, List, Optional, Union
from uuid import UUID from uuid import UUID
import yaml
from fastapi import HTTPException from fastapi import HTTPException
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.component import Component from langflow.interface.custom.component import Component
from langflow.interface.custom.directory_reader import DirectoryReader from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.services.getters import get_db_service
from langflow.interface.custom.utils import extract_inner_type, extract_union_types from langflow.interface.custom.utils import extract_inner_type, extract_union_types
from langflow.utils import validate
from langflow.services.database.utils import session_getter
from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow import Flow
import yaml from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
from langflow.utils import validate
class CustomComponent(Component): class CustomComponent(Component):
@ -54,9 +53,9 @@ class CustomComponent(Component):
reader = DirectoryReader("", False) reader = DirectoryReader("", False)
for type_hint in TYPE_HINT_LIST: for type_hint in TYPE_HINT_LIST:
if reader._is_type_hint_used_in_args( if reader._is_type_hint_used_in_args(type_hint, code) and not reader._is_type_hint_imported(
type_hint, code type_hint, code
) and not reader._is_type_hint_imported(type_hint, code): ):
error_detail = { error_detail = {
"error": "Type hint Error", "error": "Type hint Error",
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.", "traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
@ -75,20 +74,14 @@ class CustomComponent(Component):
return "" return ""
tree = self.get_code_tree(self.code) tree = self.get_code_tree(self.code)
component_classes = [ component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
cls
for cls in tree["classes"]
if self.code_class_base_inheritance in cls["bases"]
]
if not component_classes: if not component_classes:
return "" return ""
# Assume the first Component class is the one we're interested in # Assume the first Component class is the one we're interested in
component_class = component_classes[0] component_class = component_classes[0]
build_methods = [ build_methods = [
method method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
for method in component_class["methods"]
if method["name"] == self.function_entrypoint_name
] ]
if not build_methods: if not build_methods:
@ -104,8 +97,7 @@ class CustomComponent(Component):
detail={ detail={
"error": "Type hint Error", "error": "Type hint Error",
"traceback": ( "traceback": (
"Prompt type is not supported in the build method." "Prompt type is not supported in the build method." " Try using PromptTemplate instead."
" Try using PromptTemplate instead."
), ),
}, },
) )
@ -120,20 +112,14 @@ class CustomComponent(Component):
return [] return []
tree = self.get_code_tree(self.code) tree = self.get_code_tree(self.code)
component_classes = [ component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
cls
for cls in tree["classes"]
if self.code_class_base_inheritance in cls["bases"]
]
if not component_classes: if not component_classes:
return [] return []
# Assume the first Component class is the one we're interested in # Assume the first Component class is the one we're interested in
component_class = component_classes[0] component_class = component_classes[0]
build_methods = [ build_methods = [
method method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
for method in component_class["methods"]
if method["name"] == self.function_entrypoint_name
] ]
if not build_methods: if not build_methods:
@ -191,8 +177,7 @@ class CustomComponent(Component):
return validate.create_function(self.code, self.function_entrypoint_name) return validate.create_function(self.code, self.function_entrypoint_name)
def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any: def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any:
from langflow.processing.process import build_sorted_vertices from langflow.processing.process import build_sorted_vertices, process_tweaks
from langflow.processing.process import process_tweaks
db_service = get_db_service() db_service = get_db_service()
with session_getter(db_service) as session: with session_getter(db_service) as session:
@ -229,11 +214,7 @@ class CustomComponent(Component):
if flow_id: if flow_id:
flow = session.query(Flow).get(flow_id) flow = session.query(Flow).get(flow_id)
elif flow_name: elif flow_name:
flow = ( flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first()
session.query(Flow)
.filter(Flow.name == flow_name)
.filter(Flow.user_id == self.user_id)
).first()
else: else:
raise ValueError("Either flow_name or flow_id must be provided") raise ValueError("Either flow_name or flow_id must be provided")

View file

@ -76,9 +76,7 @@ class DirectoryReader:
for menu in data["menu"] for menu in data["menu"]
] ]
filtered = [menu for menu in items if menu["components"]] filtered = [menu for menu in items if menu["components"]]
logger.debug( logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}')
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
)
return {"menu": filtered} return {"menu": filtered}
def validate_code(self, file_content): def validate_code(self, file_content):
@ -111,9 +109,7 @@ class DirectoryReader:
Walk through the directory path and return a list of all .py files. Walk through the directory path and return a list of all .py files.
""" """
if not (safe_path := self.get_safe_path()): if not (safe_path := self.get_safe_path()):
raise CustomComponentPathValueError( raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.")
f"The path needs to start with '{self.base_path}'."
)
file_list = [] file_list = []
for root, _, files in os.walk(safe_path): for root, _, files in os.walk(safe_path):
@ -158,9 +154,7 @@ class DirectoryReader:
for node in ast.walk(module): for node in ast.walk(module):
if isinstance(node, ast.FunctionDef): if isinstance(node, ast.FunctionDef):
for arg in node.args.args: for arg in node.args.args:
if self._is_type_hint_in_arg_annotation( if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name):
arg.annotation, type_hint_name
):
return True return True
except SyntaxError: except SyntaxError:
# Returns False if the code is not valid Python # Returns False if the code is not valid Python
@ -178,16 +172,14 @@ class DirectoryReader:
and annotation.value.id == type_hint_name and annotation.value.id == type_hint_name
) )
def is_type_hint_used_but_not_imported( def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
self, type_hint_name: str, code: str
) -> bool:
""" """
Check if a type hint is used but not imported in the given code. Check if a type hint is used but not imported in the given code.
""" """
try: try:
return self._is_type_hint_used_in_args( return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
type_hint_name, code type_hint_name, code
) and not self._is_type_hint_imported(type_hint_name, code) )
except SyntaxError: except SyntaxError:
# Returns True if there's something wrong with the code # Returns True if there's something wrong with the code
# TODO : Find a better way to handle this # TODO : Find a better way to handle this
@ -208,9 +200,9 @@ class DirectoryReader:
return False, "Syntax error" return False, "Syntax error"
elif not self.validate_build(file_content): elif not self.validate_build(file_content):
return False, "Missing build function" return False, "Missing build function"
elif self._is_type_hint_used_in_args( elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
"Optional", file_content "Optional", file_content
) and not self._is_type_hint_imported("Optional", file_content): ):
return ( return (
False, False,
"Type hint 'Optional' is used but not imported in the code.", "Type hint 'Optional' is used but not imported in the code.",
@ -226,9 +218,7 @@ class DirectoryReader:
from the .py files in the directory. from the .py files in the directory.
""" """
response = {"menu": []} response = {"menu": []}
logger.debug( logger.debug("-------------------- Building component menu list --------------------")
"-------------------- Building component menu list --------------------"
)
for file_path in file_paths: for file_path in file_paths:
menu_name = os.path.basename(os.path.dirname(file_path)) menu_name = os.path.basename(os.path.dirname(file_path))
@ -248,9 +238,7 @@ class DirectoryReader:
# first check if it's already CamelCase # first check if it's already CamelCase
if "_" in component_name: if "_" in component_name:
component_name_camelcase = " ".join( component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
word.title() for word in component_name.split("_")
)
else: else:
component_name_camelcase = component_name component_name_camelcase = component_name
@ -266,7 +254,5 @@ class DirectoryReader:
logger.debug(f"Component info: {component_info}") logger.debug(f"Component info: {component_info}")
if menu_result not in response["menu"]: if menu_result not in response["menu"]:
response["menu"].append(menu_result) response["menu"].append(menu_result)
logger.debug( logger.debug("-------------------- Component menu list built --------------------")
"-------------------- Component menu list built --------------------"
)
return response return response

View file

@ -46,34 +46,26 @@ toolkit_type_to_cls_dict: dict[str, Any] = {
# Memories # Memories
memory_type_to_cls_dict: dict[str, Any] = { memory_type_to_cls_dict: dict[str, Any] = {
memory_name: import_class(f"langchain.memory.{memory_name}") memory_name: import_class(f"langchain.memory.{memory_name}") for memory_name in memory.__all__
for memory_name in memory.__all__
} }
# Wrappers # Wrappers
wrapper_type_to_cls_dict: dict[str, Any] = { wrapper_type_to_cls_dict: dict[str, Any] = {wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]}
wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]
}
# Embeddings # Embeddings
embedding_type_to_cls_dict: dict[str, Any] = { embedding_type_to_cls_dict: dict[str, Any] = {
embedding_name: import_class(f"langchain.embeddings.{embedding_name}") embedding_name: import_class(f"langchain.embeddings.{embedding_name}") for embedding_name in embeddings.__all__
for embedding_name in embeddings.__all__
} }
# Document Loaders # Document Loaders
documentloaders_type_to_cls_dict: dict[str, Any] = { documentloaders_type_to_cls_dict: dict[str, Any] = {
documentloader_name: import_class( documentloader_name: import_class(f"langchain.document_loaders.{documentloader_name}")
f"langchain.document_loaders.{documentloader_name}"
)
for documentloader_name in document_loaders.__all__ for documentloader_name in document_loaders.__all__
} }
# Text Splitters # Text Splitters
textsplitter_type_to_cls_dict: dict[str, Any] = dict( textsplitter_type_to_cls_dict: dict[str, Any] = dict(inspect.getmembers(text_splitter, inspect.isclass))
inspect.getmembers(text_splitter, inspect.isclass)
)
# merge CUSTOM_AGENTS and CUSTOM_CHAINS # merge CUSTOM_AGENTS and CUSTOM_CHAINS
CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore

View file

@ -1,7 +1,7 @@
from typing import Dict, List, Optional, Type from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
@ -35,8 +35,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
return [ return [
documentloader.__name__ documentloader.__name__
for documentloader in self.type_to_loader_dict.values() for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -2,7 +2,7 @@ from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import embedding_type_to_cls_dict from langflow.interface.custom_lists import embedding_type_to_cls_dict
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
@ -37,8 +37,7 @@ class EmbeddingCreator(LangChainTypeCreator):
return [ return [
embedding.__name__ embedding.__name__
for embedding in self.type_to_loader_dict.values() for embedding in self.type_to_loader_dict.values()
if embedding.__name__ in settings_service.settings.EMBEDDINGS if embedding.__name__ in settings_service.settings.EMBEDDINGS or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -104,10 +104,7 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]:
def import_wrapper(wrapper: str) -> Any: def import_wrapper(wrapper: str) -> Any:
"""Import wrapper from wrapper name""" """Import wrapper from wrapper name"""
if ( if isinstance(wrapper_creator.type_dict, dict) and wrapper in wrapper_creator.type_dict:
isinstance(wrapper_creator.type_dict, dict)
and wrapper in wrapper_creator.type_dict
):
return wrapper_creator.type_dict.get(wrapper) return wrapper_creator.type_dict.get(wrapper)

View file

@ -2,8 +2,6 @@ def initialize_vertexai(class_object, params):
if credentials_path := params.get("credentials"): if credentials_path := params.get("credentials"):
from google.oauth2 import service_account # type: ignore from google.oauth2 import service_account # type: ignore
credentials_object = service_account.Credentials.from_service_account_file( credentials_object = service_account.Credentials.from_service_account_file(filename=credentials_path)
filename=credentials_path
)
params["credentials"] = credentials_object params["credentials"] = credentials_object
return class_object(**params) return class_object(**params)

View file

@ -1,40 +1,37 @@
import json import json
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
import orjson import orjson
from typing import Any, Callable, Dict, Sequence, Type, TYPE_CHECKING
from langchain.schema import Document
from langchain.agents import agent as agent_module from langchain.agents import agent as agent_module
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
from langchain.agents.agent_toolkits.base import BaseToolkit from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.agents.tools import BaseTool from langchain.agents.tools import BaseTool
from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader
from langchain.schema import Document
from langchain.vectorstores.base import VectorStore
from loguru import logger
from pydantic import ValidationError
from langflow.interface.custom_lists import CUSTOM_NODES
from langflow.interface.importing.utils import (
get_function,
get_function_custom,
import_by_type,
)
from langflow.interface.initialize.llm import initialize_vertexai from langflow.interface.initialize.llm import initialize_vertexai
from langflow.interface.initialize.utils import ( from langflow.interface.initialize.utils import (
handle_format_kwargs, handle_format_kwargs,
handle_node_type, handle_node_type,
handle_partial_variables, handle_partial_variables,
) )
from langflow.interface.initialize.vector_store import vecstore_initializer from langflow.interface.initialize.vector_store import vecstore_initializer
from pydantic import ValidationError
from langflow.interface.importing.utils import (
get_function,
get_function_custom,
import_by_type,
)
from langflow.interface.custom_lists import CUSTOM_NODES
from langflow.interface.agents.base import agent_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.output_parsers.base import output_parser_creator from langflow.interface.output_parsers.base import output_parser_creator
from langflow.interface.retrievers.base import retriever_creator from langflow.interface.retrievers.base import retriever_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.utils import load_file_into_dict from langflow.interface.utils import load_file_into_dict
from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils import validate from langflow.utils import validate
from langchain.chains.base import Chain
from langchain.vectorstores.base import VectorStore
from langchain.document_loaders.base import BaseLoader
from loguru import logger
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow import CustomComponent from langflow import CustomComponent
@ -44,15 +41,10 @@ def build_vertex_in_params(params: Dict) -> Dict:
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
# If any of the values in params is a Vertex, we will build it # If any of the values in params is a Vertex, we will build it
return { return {key: value.build() if isinstance(value, Vertex) else value for key, value in params.items()}
key: value.build() if isinstance(value, Vertex) else value
for key, value in params.items()
}
def instantiate_class( def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None) -> Any:
node_type: str, base_type: str, params: Dict, user_id=None
) -> Any:
"""Instantiate class from module type and key, and params""" """Instantiate class from module type and key, and params"""
params = convert_params_to_sets(params) params = convert_params_to_sets(params)
params = convert_kwargs(params) params = convert_kwargs(params)
@ -64,9 +56,7 @@ def instantiate_class(
return custom_node(**params) return custom_node(**params)
logger.debug(f"Instantiating {node_type} of type {base_type}") logger.debug(f"Instantiating {node_type} of type {base_type}")
class_object = import_by_type(_type=base_type, name=node_type) class_object = import_by_type(_type=base_type, name=node_type)
return instantiate_based_on_type( return instantiate_based_on_type(class_object, base_type, node_type, params, user_id=user_id)
class_object, base_type, node_type, params, user_id=user_id
)
def convert_params_to_sets(params): def convert_params_to_sets(params):
@ -194,9 +184,7 @@ def instantiate_memory(node_type, class_object, params):
# I want to catch a specific attribute error that happens # I want to catch a specific attribute error that happens
# when the object does not have a cursor attribute # when the object does not have a cursor attribute
except Exception as exc: except Exception as exc:
if "object has no attribute 'cursor'" in str( if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
exc
) or 'object has no field "conn"' in str(exc):
raise AttributeError( raise AttributeError(
( (
"Failed to build connection to database." "Failed to build connection to database."
@ -218,6 +206,8 @@ def instantiate_retriever(node_type, class_object, params):
def instantiate_chains(node_type, class_object: Type[Chain], params: Dict): def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
from langflow.interface.chains.base import chain_creator
if "retriever" in params and hasattr(params["retriever"], "as_retriever"): if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
params["retriever"] = params["retriever"].as_retriever() params["retriever"] = params["retriever"].as_retriever()
if node_type in chain_creator.from_method_nodes: if node_type in chain_creator.from_method_nodes:
@ -230,14 +220,14 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict): def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
from langflow.interface.agents.base import agent_creator
if node_type in agent_creator.from_method_nodes: if node_type in agent_creator.from_method_nodes:
method = agent_creator.from_method_nodes[node_type] method = agent_creator.from_method_nodes[node_type]
if class_method := getattr(class_object, method, None): if class_method := getattr(class_object, method, None):
agent = class_method(**params) agent = class_method(**params)
tools = params.get("tools", []) tools = params.get("tools", [])
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
agent=agent, tools=tools, handle_parsing_errors=True
)
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)
@ -290,11 +280,7 @@ def instantiate_embedding(node_type, class_object, params: Dict):
try: try:
return class_object(**params) return class_object(**params)
except ValidationError: except ValidationError:
params = { params = {key: value for key, value in params.items() if key in class_object.model_fields}
key: value
for key, value in params.items()
if key in class_object.model_fields
}
return class_object(**params) return class_object(**params)
@ -306,9 +292,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
if "texts" in params: if "texts" in params:
params["documents"] = params.pop("texts") params["documents"] = params.pop("texts")
if "documents" in params: if "documents" in params:
params["documents"] = [ params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
doc for doc in params["documents"] if isinstance(doc, Document)
]
if initializer := vecstore_initializer.get(class_object.__name__): if initializer := vecstore_initializer.get(class_object.__name__):
vecstore = initializer(class_object, params) vecstore = initializer(class_object, params)
else: else:
@ -323,9 +307,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
return vecstore return vecstore
def instantiate_documentloader( def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
node_type: str, class_object: Type[BaseLoader], params: Dict
):
if "file_filter" in params: if "file_filter" in params:
# file_filter will be a string but we need a function # file_filter will be a string but we need a function
# that will be used to filter the files using file_filter # that will be used to filter the files using file_filter
@ -334,17 +316,13 @@ def instantiate_documentloader(
# in x and if it is, we will return True # in x and if it is, we will return True
file_filter = params.pop("file_filter") file_filter = params.pop("file_filter")
extensions = file_filter.split(",") extensions = file_filter.split(",")
params["file_filter"] = lambda x: any( params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
extension.strip() in x for extension in extensions
)
metadata = params.pop("metadata", None) metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str): if metadata and isinstance(metadata, str):
try: try:
metadata = orjson.loads(metadata) metadata = orjson.loads(metadata)
except json.JSONDecodeError as exc: except json.JSONDecodeError as exc:
raise ValueError( raise ValueError("The metadata you provided is not a valid JSON string.") from exc
"The metadata you provided is not a valid JSON string."
) from exc
if node_type == "WebBaseLoader": if node_type == "WebBaseLoader":
if web_path := params.pop("web_path", None): if web_path := params.pop("web_path", None):
@ -377,16 +355,12 @@ def instantiate_textsplitter(
"Try changing the chunk_size of the Text Splitter." "Try changing the chunk_size of the Text Splitter."
) from exc ) from exc
if ( if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
"separator_type" in params and params["separator_type"] == "Text"
) or "separator_type" not in params:
params.pop("separator_type", None) params.pop("separator_type", None)
# separators might come in as an escaped string like \\n # separators might come in as an escaped string like \\n
# so we need to convert it to a string # so we need to convert it to a string
if "separators" in params: if "separators" in params:
params["separators"] = ( params["separators"] = params["separators"].encode().decode("unicode-escape")
params["separators"].encode().decode("unicode-escape")
)
text_splitter = class_object(**params) text_splitter = class_object(**params)
else: else:
from langchain.text_splitter import Language from langchain.text_splitter import Language
@ -413,8 +387,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
tools = [ tools = [
tool tool
for tool in nodes for tool in nodes
if tool["type"] != "chatOutputNode" if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
and "Tool" in tool["data"]["node"]["base_classes"]
] ]
node["data"] = build_prompt_template(prompt=node["data"], tools=tools) node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
break break
@ -428,9 +401,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
# agent has hidden args for memory. might need to be support # agent has hidden args for memory. might need to be support
# memory = params["memory"] # memory = params["memory"]
# if allowed_tools is not a list or set, make it a list # if allowed_tools is not a list or set, make it a list
if not isinstance(allowed_tools, (list, set)) and isinstance( if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
allowed_tools, BaseTool
):
allowed_tools = [allowed_tools] allowed_tools = [allowed_tools]
tool_names = [tool.name for tool in allowed_tools] tool_names = [tool.name for tool in allowed_tools]
# Agent class requires an output_parser but Agent classes # Agent class requires an output_parser but Agent classes
@ -458,10 +429,7 @@ def build_prompt_template(prompt, tools):
format_instructions = prompt["node"]["template"]["format_instructions"]["value"] format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
tool_strings = "\n".join( tool_strings = "\n".join(
[ [f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools]
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
for tool in tools
]
) )
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools]) tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
format_instructions = format_instructions.format(tool_names=tool_names) format_instructions = format_instructions.format(tool_names=tool_names)

View file

@ -30,9 +30,7 @@ def check_tools_in_params(params: Dict):
def instantiate_from_template(class_object, params: Dict): def instantiate_from_template(class_object, params: Dict):
from_template_params = { from_template_params = {"template": params.pop("prompt", params.pop("template", ""))}
"template": params.pop("prompt", params.pop("template", ""))
}
if not from_template_params.get("template"): if not from_template_params.get("template"):
raise ValueError("Prompt template is required") raise ValueError("Prompt template is required")
return class_object.from_template(**from_template_params) return class_object.from_template(**from_template_params)
@ -48,9 +46,7 @@ def handle_format_kwargs(prompt, params: Dict):
def handle_partial_variables(prompt, format_kwargs: Dict): def handle_partial_variables(prompt, format_kwargs: Dict):
partial_variables = format_kwargs.copy() partial_variables = format_kwargs.copy()
partial_variables = { partial_variables = {key: value for key, value in partial_variables.items() if value}
key: value for key, value in partial_variables.items() if value
}
# Remove handle_keys otherwise LangChain raises an error # Remove handle_keys otherwise LangChain raises an error
partial_variables.pop("handle_keys", None) partial_variables.pop("handle_keys", None)
if partial_variables and hasattr(prompt, "partial"): if partial_variables and hasattr(prompt, "partial"):
@ -62,9 +58,7 @@ def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
variable = params[input_variable] variable = params[input_variable]
if isinstance(variable, str): if isinstance(variable, str):
format_kwargs[input_variable] = variable format_kwargs[input_variable] = variable
elif isinstance(variable, BaseOutputParser) and hasattr( elif isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions"):
variable, "get_format_instructions"
):
format_kwargs[input_variable] = variable.get_format_instructions() format_kwargs[input_variable] = variable.get_format_instructions()
elif is_instance_of_list_or_document(variable): elif is_instance_of_list_or_document(variable):
format_kwargs = format_document(variable, input_variable, format_kwargs) format_kwargs = format_document(variable, input_variable, format_kwargs)
@ -107,8 +101,7 @@ def try_to_load_json(content):
def needs_handle_keys(variable): def needs_handle_keys(variable):
return is_instance_of_list_or_document(variable) or ( return is_instance_of_list_or_document(variable) or (
isinstance(variable, BaseOutputParser) isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions")
and hasattr(variable, "get_format_instructions")
) )

View file

@ -17,9 +17,7 @@ import orjson
def docs_in_params(params: dict) -> bool: def docs_in_params(params: dict) -> bool:
"""Check if params has documents OR texts and one of them is not an empty list, """Check if params has documents OR texts and one of them is not an empty list,
If any of them is not an empty list, return True, else return False""" If any of them is not an empty list, return True, else return False"""
return ("documents" in params and params["documents"]) or ( return ("documents" in params and params["documents"]) or ("texts" in params and params["texts"])
"texts" in params and params["texts"]
)
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict): def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
@ -31,9 +29,7 @@ def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dic
from pymongo import MongoClient from pymongo import MongoClient
import certifi import certifi
client: MongoClient = MongoClient( client: MongoClient = MongoClient(MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where())
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
)
db_name = params.pop("db_name", None) db_name = params.pop("db_name", None)
collection_name = params.pop("collection_name", None) collection_name = params.pop("collection_name", None)
if not db_name or not collection_name: if not db_name or not collection_name:
@ -141,9 +137,7 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
pinecone_env = os.getenv("PINECONE_ENV") pinecone_env = os.getenv("PINECONE_ENV")
if pinecone_api_key is None or pinecone_env is None: if pinecone_api_key is None or pinecone_env is None:
raise ValueError( raise ValueError("Pinecone API key and environment must be provided in the params")
"Pinecone API key and environment must be provided in the params"
)
# initialize pinecone # initialize pinecone
pinecone.init( pinecone.init(
@ -177,19 +171,13 @@ def initialize_chroma(class_object: Type[Chroma], params: dict):
import chromadb # type: ignore import chromadb # type: ignore
settings_params = { settings_params = {
key: params[key] key: params[key] for key, value_ in params.items() if key.startswith("chroma_server_") and value_
for key, value_ in params.items()
if key.startswith("chroma_server_") and value_
} }
chroma_settings = chromadb.config.Settings(**settings_params) chroma_settings = chromadb.config.Settings(**settings_params)
params["client_settings"] = chroma_settings params["client_settings"] = chroma_settings
else: else:
# remove all chroma_server_ keys from params # remove all chroma_server_ keys from params
params = { params = {key: value for key, value in params.items() if not key.startswith("chroma_server_")}
key: value
for key, value in params.items()
if not key.startswith("chroma_server_")
}
persist = params.pop("persist", False) persist = params.pop("persist", False)
if not docs_in_params(params): if not docs_in_params(params):

View file

@ -1,4 +1,4 @@
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.utils.lazy_load import LazyLoadDictBase from langflow.utils.lazy_load import LazyLoadDictBase

View file

@ -2,7 +2,7 @@ from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import llm_type_to_cls_dict from langflow.interface.custom_lists import llm_type_to_cls_dict
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.llms import LLMFrontendNode from langflow.template.frontend_node.llms import LLMFrontendNode
from loguru import logger from loguru import logger
@ -38,8 +38,7 @@ class LLMCreator(LangChainTypeCreator):
return [ return [
llm.__name__ llm.__name__
for llm in self.type_to_loader_dict.values() for llm in self.type_to_loader_dict.values()
if llm.__name__ in settings_service.settings.LLMS if llm.__name__ in settings_service.settings.LLMS or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -2,7 +2,7 @@ from typing import ClassVar, Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import memory_type_to_cls_dict from langflow.interface.custom_lists import memory_type_to_cls_dict
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.memories import MemoryFrontendNode from langflow.template.frontend_node.memories import MemoryFrontendNode
@ -53,8 +53,7 @@ class MemoryCreator(LangChainTypeCreator):
return [ return [
memory.__name__ memory.__name__
for memory in self.type_to_loader_dict.values() for memory in self.type_to_loader_dict.values()
if memory.__name__ in settings_service.settings.MEMORIES if memory.__name__ in settings_service.settings.MEMORIES or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -4,7 +4,7 @@ from langchain import output_parsers
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
from loguru import logger from loguru import logger
@ -26,17 +26,14 @@ class OutputParserCreator(LangChainTypeCreator):
if self.type_dict is None: if self.type_dict is None:
settings_service = get_settings_service() settings_service = get_settings_service()
self.type_dict = { self.type_dict = {
output_parser_name: import_class( output_parser_name: import_class(f"langchain.output_parsers.{output_parser_name}")
f"langchain.output_parsers.{output_parser_name}"
)
# if output_parser_name is not lower case it is a class # if output_parser_name is not lower case it is a class
for output_parser_name in output_parsers.__all__ for output_parser_name in output_parsers.__all__
} }
self.type_dict = { self.type_dict = {
name: output_parser name: output_parser
for name, output_parser in self.type_dict.items() for name, output_parser in self.type_dict.items()
if name in settings_service.settings.OUTPUT_PARSERS if name in settings_service.settings.OUTPUT_PARSERS or settings_service.settings.DEV
or settings_service.settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -5,7 +5,7 @@ from langchain import prompts
from langflow.custom.customs import get_custom_nodes from langflow.custom.customs import get_custom_nodes
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.prompts import PromptFrontendNode from langflow.template.frontend_node.prompts import PromptFrontendNode
from loguru import logger from loguru import logger
@ -36,8 +36,7 @@ class PromptCreator(LangChainTypeCreator):
self.type_dict = { self.type_dict = {
name: prompt name: prompt
for name, prompt in self.type_dict.items() for name, prompt in self.type_dict.items()
if name in settings_service.settings.PROMPTS if name in settings_service.settings.PROMPTS or settings_service.settings.DEV
or settings_service.settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -42,17 +42,13 @@ class BaseCustomPrompt(PromptTemplate):
values["template"] = values["template"].format(**format_dict) values["template"] = values["template"].format(**format_dict)
values["template"] = values["template"] values["template"] = values["template"]
values["input_variables"] = extract_input_variables_from_prompt( values["input_variables"] = extract_input_variables_from_prompt(values["template"])
values["template"]
)
return values return values
class SeriesCharacterPrompt(BaseCustomPrompt): class SeriesCharacterPrompt(BaseCustomPrompt):
# Add a very descriptive description for the prompt generator # Add a very descriptive description for the prompt generator
description: Optional[ description: Optional[str] = "A prompt that asks the AI to act like a character from a series."
str
] = "A prompt that asks the AI to act like a character from a series."
character: str character: str
series: str series: str
template: str = """I want you to act like {character} from {series}. template: str = """I want you to act like {character} from {series}.
@ -68,6 +64,4 @@ Human: {input}
input_variables: List[str] = ["character", "series"] input_variables: List[str] = ["character", "series"]
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = { CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {"SeriesCharacterPrompt": SeriesCharacterPrompt}
"SeriesCharacterPrompt": SeriesCharacterPrompt
}

View file

@ -4,7 +4,7 @@ from langchain import retrievers
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
from loguru import logger from loguru import logger
@ -42,9 +42,7 @@ class RetrieverCreator(LangChainTypeCreator):
method_name=self.from_method_nodes[name], method_name=self.from_method_nodes[name],
) )
else: else:
return build_template_from_class( return build_template_from_class(name, type_to_cls_dict=self.type_to_loader_dict)
name, type_to_cls_dict=self.type_to_loader_dict
)
except ValueError as exc: except ValueError as exc:
raise ValueError(f"Retriever {name} not found") from exc raise ValueError(f"Retriever {name} not found") from exc
except AttributeError as exc: except AttributeError as exc:
@ -56,8 +54,7 @@ class RetrieverCreator(LangChainTypeCreator):
return [ return [
retriever retriever
for retriever in self.type_to_loader_dict.keys() for retriever in self.type_to_loader_dict.keys()
if retriever in settings_service.settings.RETRIEVERS if retriever in settings_service.settings.RETRIEVERS or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -4,9 +4,7 @@ from loguru import logger
from uuid import UUID from uuid import UUID
def build_sorted_vertices( def build_sorted_vertices(data_graph, user_id: Optional[Union[str, UUID]] = None) -> Tuple[Graph, Dict]:
data_graph, user_id: Optional[Union[str, UUID]] = None
) -> Tuple[Graph, Dict]:
""" """
Build langchain object from data_graph. Build langchain object from data_graph.
""" """

View file

@ -1,7 +1,7 @@
from typing import Dict, List, Optional, Type from typing import Dict, List, Optional, Type
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
@ -35,8 +35,7 @@ class TextSplitterCreator(LangChainTypeCreator):
return [ return [
textsplitter.__name__ textsplitter.__name__
for textsplitter in self.type_to_loader_dict.values() for textsplitter in self.type_to_loader_dict.values()
if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -4,7 +4,7 @@ from langchain.agents import agent_toolkits
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class, import_module from langflow.interface.importing.utils import import_class, import_module
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from loguru import logger from loguru import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -32,13 +32,10 @@ class ToolkitCreator(LangChainTypeCreator):
if self.type_dict is None: if self.type_dict is None:
settings_service = get_settings_service() settings_service = get_settings_service()
self.type_dict = { self.type_dict = {
toolkit_name: import_class( toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}")
f"langchain.agents.agent_toolkits.{toolkit_name}"
)
# if toolkit_name is not lower case it is a class # if toolkit_name is not lower case it is a class
for toolkit_name in agent_toolkits.__all__ for toolkit_name in agent_toolkits.__all__
if not toolkit_name.islower() if not toolkit_name.islower() and toolkit_name in settings_service.settings.TOOLKITS
and toolkit_name in settings_service.settings.TOOLKITS
} }
return self.type_dict return self.type_dict
@ -61,9 +58,7 @@ class ToolkitCreator(LangChainTypeCreator):
def get_create_function(self, name: str) -> Callable: def get_create_function(self, name: str) -> Callable:
if loader_name := self.create_functions.get(name): if loader_name := self.create_functions.get(name):
return import_module( return import_module(f"from langchain.agents.agent_toolkits import {loader_name[0]}")
f"from langchain.agents.agent_toolkits import {loader_name[0]}"
)
else: else:
raise ValueError("Toolkit not found") raise ValueError("Toolkit not found")

View file

@ -15,7 +15,7 @@ from langflow.interface.tools.constants import (
OTHER_TOOLS, OTHER_TOOLS,
) )
from langflow.interface.tools.util import get_tool_params from langflow.interface.tools.util import get_tool_params
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.field.base import TemplateField from langflow.template.field.base import TemplateField
from langflow.template.template.base import Template from langflow.template.template.base import Template
@ -32,9 +32,7 @@ TOOL_INPUTS = {
placeholder="", placeholder="",
value="", value="",
), ),
"llm": TemplateField( "llm": TemplateField(field_type="BaseLanguageModel", required=True, is_list=False, show=True),
field_type="BaseLanguageModel", required=True, is_list=False, show=True
),
"func": TemplateField( "func": TemplateField(
field_type="Callable", field_type="Callable",
required=True, required=True,
@ -81,10 +79,7 @@ class ToolCreator(LangChainTypeCreator):
tool_name = tool_params.get("name") or tool tool_name = tool_params.get("name") or tool
if ( if tool_name in settings_service.settings.TOOLS or settings_service.settings.DEV:
tool_name in settings_service.settings.TOOLS
or settings_service.settings.DEV
):
if tool_name == "JsonSpec": if tool_name == "JsonSpec":
tool_params["path"] = tool_params.pop("dict_") # type: ignore tool_params["path"] = tool_params.pop("dict_") # type: ignore
all_tools[tool_name] = { all_tools[tool_name] = {

View file

@ -21,16 +21,12 @@ def get_func_tool_params(func, **kwargs) -> Union[Dict, None]:
for keyword in tool.keywords: for keyword in tool.keywords:
if keyword.arg == "name": if keyword.arg == "name":
try: try:
tool_params["name"] = ast.literal_eval( tool_params["name"] = ast.literal_eval(keyword.value)
keyword.value
)
except ValueError: except ValueError:
break break
elif keyword.arg == "description": elif keyword.arg == "description":
try: try:
tool_params["description"] = ast.literal_eval( tool_params["description"] = ast.literal_eval(keyword.value)
keyword.value
)
except ValueError: except ValueError:
continue continue
@ -43,9 +39,7 @@ def get_func_tool_params(func, **kwargs) -> Union[Dict, None]:
else: else:
# get the class object from the return statement # get the class object from the return statement
try: try:
class_obj = eval( class_obj = eval(compile(ast.Expression(tool), "<string>", "eval"))
compile(ast.Expression(tool), "<string>", "eval")
)
except Exception: except Exception:
return None return None

View file

@ -7,6 +7,8 @@ from typing import Any, Dict, List, Optional, Union
from uuid import UUID from uuid import UUID
from fastapi import HTTPException from fastapi import HTTPException
from loguru import logger
from langflow.api.utils import get_new_key from langflow.api.utils import get_new_key
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
@ -33,7 +35,6 @@ from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
from langflow.utils.util import get_base_classes from langflow.utils.util import get_base_classes
from loguru import logger
# Used to get the base_classes list # Used to get the base_classes list

View file

@ -1,14 +1,13 @@
from typing import Dict, List, Optional, Type from typing import Dict, List, Optional, Type
from langchain import utilities from langchain import utilities
from loguru import logger
from langflow.custom.customs import get_custom_nodes from langflow.custom.customs import get_custom_nodes
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
from loguru import logger
from langflow.utils.util import build_template_from_class from langflow.utils.util import build_template_from_class
@ -41,8 +40,7 @@ class UtilityCreator(LangChainTypeCreator):
self.type_dict = { self.type_dict = {
name: utility name: utility
for name, utility in self.type_dict.items() for name, utility in self.type_dict.items()
if name in settings_service.settings.UTILITIES if name in settings_service.settings.UTILITIES or settings_service.settings.DEV
or settings_service.settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -10,7 +10,7 @@ from langchain.base_language import BaseLanguageModel
from PIL.Image import Image from PIL.Image import Image
from loguru import logger from loguru import logger
from langflow.services.chat.config import ChatConfig from langflow.services.chat.config import ChatConfig
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
def load_file_into_dict(file_path: str) -> dict: def load_file_into_dict(file_path: str) -> dict:
@ -43,9 +43,7 @@ def try_setting_streaming_options(langchain_object):
llm = None llm = None
if hasattr(langchain_object, "llm"): if hasattr(langchain_object, "llm"):
llm = langchain_object.llm llm = langchain_object.llm
elif hasattr(langchain_object, "llm_chain") and hasattr( elif hasattr(langchain_object, "llm_chain") and hasattr(langchain_object.llm_chain, "llm"):
langchain_object.llm_chain, "llm"
):
llm = langchain_object.llm_chain.llm llm = langchain_object.llm_chain.llm
if isinstance(llm, BaseLanguageModel): if isinstance(llm, BaseLanguageModel):
@ -79,9 +77,7 @@ def set_langchain_cache(settings):
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"): if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
try: try:
cache_class = import_class( cache_class = import_class(f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}")
f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}"
)
logger.debug(f"Setting up LLM caching with {cache_class.__name__}") logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
langchain.llm_cache = cache_class() langchain.llm_cache = cache_class()

View file

@ -4,7 +4,7 @@ from langchain import vectorstores
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
from loguru import logger from loguru import logger
@ -22,9 +22,7 @@ class VectorstoreCreator(LangChainTypeCreator):
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
self.type_dict: dict[str, Any] = { self.type_dict: dict[str, Any] = {
vectorstore_name: import_class( vectorstore_name: import_class(f"langchain.vectorstores.{vectorstore_name}")
f"langchain.vectorstores.{vectorstore_name}"
)
for vectorstore_name in vectorstores.__all__ for vectorstore_name in vectorstores.__all__
} }
return self.type_dict return self.type_dict
@ -48,8 +46,7 @@ class VectorstoreCreator(LangChainTypeCreator):
return [ return [
vectorstore vectorstore
for vectorstore in self.type_to_loader_dict.keys() for vectorstore in self.type_to_loader_dict.keys()
if vectorstore in settings_service.settings.VECTORSTORES if vectorstore in settings_service.settings.VECTORSTORES or settings_service.settings.DEV
or settings_service.settings.DEV
] ]

View file

@ -16,8 +16,7 @@ class WrapperCreator(LangChainTypeCreator):
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
self.type_dict = { self.type_dict = {
wrapper.__name__: wrapper wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
} }
return self.type_dict return self.type_dict

View file

@ -1,19 +1,16 @@
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from fastapi import FastAPI from urllib.parse import urlencode
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from langflow.api import router from langflow.api import router
from langflow.interface.utils import setup_llm_caching from langflow.interface.utils import setup_llm_caching
from langflow.services.utils import initialize_services
from langflow.services.plugins.langfuse import LangfuseInstance from langflow.services.plugins.langfuse import LangfuseInstance
from langflow.services.utils import ( from langflow.services.utils import initialize_services, teardown_services
teardown_services,
)
from langflow.utils.logger import configure from langflow.utils.logger import configure
@ -23,7 +20,6 @@ def create_app():
configure() configure()
app = FastAPI() app = FastAPI()
origins = ["*"] origins = ["*"]
app.add_middleware( app.add_middleware(
@ -34,6 +30,16 @@ def create_app():
allow_headers=["*"], allow_headers=["*"],
) )
@app.middleware("http")
async def flatten_query_string_lists(request: Request, call_next):
flattened = []
for key, value in request.query_params.multi_items():
flattened.extend((key, entry) for entry in value.split(","))
request.scope["query_string"] = urlencode(flattened, doseq=True).encode("utf-8")
return await call_next(request)
@app.get("/health") @app.get("/health")
def health(): def health():
return {"status": "ok"} return {"status": "ok"}
@ -78,9 +84,7 @@ def get_static_files_dir():
return frontend_path / "frontend" return frontend_path / "frontend"
def setup_app( def setup_app(static_files_dir: Optional[Path] = None, backend_only: bool = False) -> FastAPI:
static_files_dir: Optional[Path] = None, backend_only: bool = False
) -> FastAPI:
"""Setup the FastAPI app.""" """Setup the FastAPI app."""
# get the directory of the current file # get the directory of the current file
if not static_files_dir: if not static_files_dir:
@ -96,6 +100,7 @@ def setup_app(
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
from langflow.__main__ import get_number_of_workers from langflow.__main__ import get_number_of_workers
configure() configure()

View file

@ -34,9 +34,7 @@ def get_langfuse_callback(trace_id):
if langfuse := LangfuseInstance.get(): if langfuse := LangfuseInstance.get():
logger.debug("Langfuse credentials found") logger.debug("Langfuse credentials found")
try: try:
trace = langfuse.trace( trace = langfuse.trace(CreateTrace(name="langflow-" + trace_id, id=trace_id))
CreateTrace(name="langflow-" + trace_id, id=trace_id)
)
return trace.getNewHandler() return trace.getNewHandler()
except Exception as exc: except Exception as exc:
logger.error(f"Error initializing langfuse callback: {exc}") logger.error(f"Error initializing langfuse callback: {exc}")
@ -44,9 +42,7 @@ def get_langfuse_callback(trace_id):
return None return None
def flush_langfuse_callback_if_present( def flush_langfuse_callback_if_present(callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]]):
callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]]
):
""" """
If langfuse callback is present, run callback.langfuse.flush() If langfuse callback is present, run callback.langfuse.flush()
""" """
@ -88,15 +84,9 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
# if langfuse callback is present, run callback.langfuse.flush() # if langfuse callback is present, run callback.langfuse.flush()
flush_langfuse_callback_if_present(callbacks) flush_langfuse_callback_if_present(callbacks)
intermediate_steps = ( intermediate_steps = output.get("intermediate_steps", []) if isinstance(output, dict) else []
output.get("intermediate_steps", []) if isinstance(output, dict) else []
)
result = ( result = output.get(langchain_object.output_keys[0]) if isinstance(output, dict) else output
output.get(langchain_object.output_keys[0])
if isinstance(output, dict)
else output
)
try: try:
thought = format_actions(intermediate_steps) if intermediate_steps else "" thought = format_actions(intermediate_steps) if intermediate_steps else ""
except Exception as exc: except Exception as exc:

View file

@ -6,7 +6,7 @@ from langflow.interface.run import (
get_memory_key, get_memory_key,
update_memory_keys, update_memory_keys,
) )
from langflow.services.getters import get_session_service from langflow.services.deps import get_session_service
from loguru import logger from loguru import logger
from langflow.graph import Graph from langflow.graph import Graph
from langchain.chains.base import Chain from langchain.chains.base import Chain
@ -112,9 +112,7 @@ def load_langchain_object(
logger.debug("Loaded LangChain object") logger.debug("Loaded LangChain object")
if langchain_object is None: if langchain_object is None:
raise ValueError( raise ValueError("There was an error loading the langchain_object. Please, check all the nodes and try again.")
"There was an error loading the langchain_object. Please, check all the nodes and try again."
)
return langchain_object, artifacts, session_id return langchain_object, artifacts, session_id
@ -164,9 +162,7 @@ async def process_graph_cached(
if clear_cache: if clear_cache:
session_service.clear_session(session_id) session_service.clear_session(session_id)
if session_id is None: if session_id is None:
session_id = session_service.generate_key( session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
session_id=session_id, data_graph=data_graph
)
# Load the graph using SessionService # Load the graph using SessionService
graph, artifacts = session_service.load_session(session_id, data_graph) graph, artifacts = session_service.load_session(session_id, data_graph)
built_object = graph.build() built_object = graph.build()
@ -179,9 +175,7 @@ async def process_graph_cached(
return Result(result=result, session_id=session_id) return Result(result=result, session_id=session_id)
def load_flow_from_json( def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True):
flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
):
""" """
Load flow from a JSON file or a JSON object. Load flow from a JSON file or a JSON object.
@ -198,9 +192,7 @@ def load_flow_from_json(
elif isinstance(flow, dict): elif isinstance(flow, dict):
flow_graph = flow flow_graph = flow
else: else:
raise TypeError( raise TypeError("Input must be either a file path (str) or a JSON object (dict)")
"Input must be either a file path (str) or a JSON object (dict)"
)
graph_data = flow_graph["data"] graph_data = flow_graph["data"]
if tweaks is not None: if tweaks is not None:
@ -226,18 +218,14 @@ def load_flow_from_json(
return graph return graph
def validate_input( def validate_input(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
) -> List[Dict[str, Any]]:
if not isinstance(graph_data, dict) or not isinstance(tweaks, dict): if not isinstance(graph_data, dict) or not isinstance(tweaks, dict):
raise ValueError("graph_data and tweaks should be dictionaries") raise ValueError("graph_data and tweaks should be dictionaries")
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes") nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
if not isinstance(nodes, list): if not isinstance(nodes, list):
raise ValueError( raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key")
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
)
return nodes return nodes
@ -246,9 +234,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
template_data = node.get("data", {}).get("node", {}).get("template") template_data = node.get("data", {}).get("node", {}).get("template")
if not isinstance(template_data, dict): if not isinstance(template_data, dict):
logger.warning( logger.warning(f"Template data for node {node.get('id')} should be a dictionary")
f"Template data for node {node.get('id')} should be a dictionary"
)
return return
for tweak_name, tweak_value in node_tweaks.items(): for tweak_name, tweak_value in node_tweaks.items():
@ -257,9 +243,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
template_data[tweak_name][key] = tweak_value template_data[tweak_name][key] = tweak_value
def process_tweaks( def process_tweaks(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
) -> Dict[str, Any]:
""" """
This function is used to tweak the graph data using the node id and the tweaks dict. This function is used to tweak the graph data using the node id and the tweaks dict.
@ -280,8 +264,6 @@ def process_tweaks(
if node_tweaks := tweaks.get(node_id): if node_tweaks := tweaks.get(node_id):
apply_tweaks(node, node_tweaks) apply_tweaks(node, node_tweaks)
else: else:
logger.warning( logger.warning("Each node should be a dictionary with an 'id' key of type str")
"Each node should be a dictionary with an 'id' key of type str"
)
return graph_data return graph_data

View file

@ -10,11 +10,7 @@ class LangflowApplication(BaseApplication):
super().__init__() super().__init__()
def load_config(self): def load_config(self):
config = { config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None}
key: value
for key, value in self.options.items()
if key in self.cfg.settings and value is not None
}
for key, value in config.items(): for key, value in config.items():
self.cfg.set(key.lower(), value) self.cfg.set(key.lower(), value)

View file

@ -2,7 +2,7 @@ from langflow.services.base import Service
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.settings.manager import SettingsService from langflow.services.settings.service import SettingsService
class AuthService(Service): class AuthService(Service):

View file

@ -12,19 +12,16 @@ from langflow.services.database.models.user.crud import (
get_user_by_username, get_user_by_username,
update_user_last_login_at, update_user_last_login_at,
) )
from langflow.services.getters import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
from sqlmodel import Session from sqlmodel import Session
from cryptography.fernet import Fernet
oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login", auto_error=False) oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login", auto_error=False)
API_KEY_NAME = "x-api-key" API_KEY_NAME = "x-api-key"
api_key_query = APIKeyQuery( api_key_query = APIKeyQuery(name=API_KEY_NAME, scheme_name="API key query", auto_error=False)
name=API_KEY_NAME, scheme_name="API key query", auto_error=False api_key_header = APIKeyHeader(name=API_KEY_NAME, scheme_name="API key header", auto_error=False)
)
api_key_header = APIKeyHeader(
name=API_KEY_NAME, scheme_name="API key header", auto_error=False
)
# Source: https://github.com/mrtolkien/fastapi_simple_security/blob/master/fastapi_simple_security/security_api_key.py # Source: https://github.com/mrtolkien/fastapi_simple_security/blob/master/fastapi_simple_security/security_api_key.py
@ -141,23 +138,17 @@ def get_current_active_user(current_user: Annotated[User, Depends(get_current_us
return current_user return current_user
def get_current_active_superuser( def get_current_active_superuser(current_user: Annotated[User, Depends(get_current_user)]) -> User:
current_user: Annotated[User, Depends(get_current_user)]
) -> User:
if not current_user.is_active: if not current_user.is_active:
raise HTTPException(status_code=401, detail="Inactive user") raise HTTPException(status_code=401, detail="Inactive user")
if not current_user.is_superuser: if not current_user.is_superuser:
raise HTTPException( raise HTTPException(status_code=400, detail="The user doesn't have enough privileges")
status_code=400, detail="The user doesn't have enough privileges"
)
return current_user return current_user
def verify_password(plain_password, hashed_password): def verify_password(plain_password, hashed_password):
settings_service = get_settings_service() settings_service = get_settings_service()
return settings_service.auth_settings.pwd_context.verify( return settings_service.auth_settings.pwd_context.verify(plain_password, hashed_password)
plain_password, hashed_password
)
def get_password_hash(password): def get_password_hash(password):
@ -246,22 +237,16 @@ def get_user_id_from_token(token: str) -> UUID:
return UUID(int=0) return UUID(int=0)
def create_user_tokens( def create_user_tokens(user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False) -> dict:
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
) -> dict:
settings_service = get_settings_service() settings_service = get_settings_service()
access_token_expires = timedelta( access_token_expires = timedelta(minutes=settings_service.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES)
minutes=settings_service.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
access_token = create_token( access_token = create_token(
data={"sub": str(user_id)}, data={"sub": str(user_id)},
expires_delta=access_token_expires, expires_delta=access_token_expires,
) )
refresh_token_expires = timedelta( refresh_token_expires = timedelta(minutes=settings_service.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES)
minutes=settings_service.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES
)
refresh_token = create_token( refresh_token = create_token(
data={"sub": str(user_id), "type": "rf"}, data={"sub": str(user_id), "type": "rf"},
expires_delta=refresh_token_expires, expires_delta=refresh_token_expires,
@ -291,9 +276,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session))
token_type: str = payload.get("type") # type: ignore token_type: str = payload.get("type") # type: ignore
if user_id is None or token_type is None: if user_id is None or token_type is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token")
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token"
)
return create_user_tokens(user_id, db) return create_user_tokens(user_id, db)
@ -304,9 +287,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session))
) from e ) from e
def authenticate_user( def authenticate_user(username: str, password: str, db: Session = Depends(get_session)) -> Optional[User]:
username: str, password: str, db: Session = Depends(get_session)
) -> Optional[User]:
user = get_user_by_username(db, username) user = get_user_by_username(db, username)
if not user: if not user:
@ -318,3 +299,33 @@ def authenticate_user(
raise HTTPException(status_code=400, detail="Inactive user") raise HTTPException(status_code=400, detail="Inactive user")
return user if verify_password(password, user.password) else None return user if verify_password(password, user.password) else None
def add_padding(s):
# Calculate the number of padding characters needed
padding_needed = 4 - len(s) % 4
return s + "=" * padding_needed
def get_fernet(settings_service=Depends(get_settings_service)):
SECRET_KEY = settings_service.auth_settings.SECRET_KEY
# It's important that your secret key is 32 url-safe base64-encoded bytes
padded_secret_key = add_padding(SECRET_KEY)
fernet = Fernet(padded_secret_key)
return fernet
def encrypt_api_key(api_key: str, settings_service=Depends(get_settings_service)):
fernet = get_fernet(settings_service)
# Two-way encryption
encrypted_key = fernet.encrypt(api_key.encode())
return encrypted_key
def decrypt_api_key(encrypted_api_key: str, settings_service=Depends(get_settings_service)):
fernet = get_fernet(settings_service)
# Two-way decryption
if isinstance(encrypted_api_key, str):
encrypted_api_key = encrypted_api_key.encode()
decrypted_key = fernet.decrypt(encrypted_api_key).decode()
return decrypted_key

View file

@ -1,9 +1,9 @@
from . import factory, manager from . import factory, service
from langflow.services.cache.manager import InMemoryCache from langflow.services.cache.service import InMemoryCache
__all__ = [ __all__ = [
"factory", "factory",
"manager", "service",
"InMemoryCache", "InMemoryCache",
] ]

View file

@ -1,10 +1,10 @@
from langflow.services.cache.manager import InMemoryCache, RedisCache, BaseCacheService from langflow.services.cache.service import InMemoryCache, RedisCache, BaseCacheService
from langflow.services.factory import ServiceFactory from langflow.services.factory import ServiceFactory
from langflow.utils.logger import logger from langflow.utils.logger import logger
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.settings.manager import SettingsService from langflow.services.settings.service import SettingsService
class CacheServiceFactory(ServiceFactory): class CacheServiceFactory(ServiceFactory):
@ -26,9 +26,7 @@ class CacheServiceFactory(ServiceFactory):
if redis_cache.is_connected(): if redis_cache.is_connected():
logger.debug("Redis cache is connected") logger.debug("Redis cache is connected")
return redis_cache return redis_cache
logger.warning( logger.warning("Redis cache is not connected, falling back to in-memory cache")
"Redis cache is not connected, falling back to in-memory cache"
)
return InMemoryCache() return InMemoryCache()
elif settings_service.settings.CACHE_TYPE == "memory": elif settings_service.settings.CACHE_TYPE == "memory":

View file

@ -68,10 +68,7 @@ class InMemoryCache(BaseCacheService, Service):
Retrieve an item from the cache without acquiring the lock. Retrieve an item from the cache without acquiring the lock.
""" """
if item := self._cache.get(key): if item := self._cache.get(key):
if ( if self.expiration_time is None or time.time() - item["time"] < self.expiration_time:
self.expiration_time is None
or time.time() - item["time"] < self.expiration_time
):
# Move the key to the end to make it recently used # Move the key to the end to make it recently used
self._cache.move_to_end(key) self._cache.move_to_end(key)
# Check if the value is pickled # Check if the value is pickled
@ -118,11 +115,7 @@ class InMemoryCache(BaseCacheService, Service):
""" """
with self._lock: with self._lock:
existing_value = self._get_without_lock(key) existing_value = self._get_without_lock(key)
if ( if existing_value is not None and isinstance(existing_value, dict) and isinstance(value, dict):
existing_value is not None
and isinstance(existing_value, dict)
and isinstance(value, dict)
):
existing_value.update(value) existing_value.update(value)
value = existing_value value = existing_value
@ -276,9 +269,7 @@ class RedisCache(BaseCacheService, Service):
if not result: if not result:
raise ValueError("RedisCache could not set the value.") raise ValueError("RedisCache could not set the value.")
except TypeError as exc: except TypeError as exc:
raise TypeError( raise TypeError("RedisCache only accepts values that can be pickled. ") from exc
"RedisCache only accepts values that can be pickled. "
) from exc
def upsert(self, key, value): def upsert(self, key, value):
""" """
@ -290,11 +281,7 @@ class RedisCache(BaseCacheService, Service):
value: The value to insert or update. value: The value to insert or update.
""" """
existing_value = self.get(key) existing_value = self.get(key)
if ( if existing_value is not None and isinstance(existing_value, dict) and isinstance(value, dict):
existing_value is not None
and isinstance(existing_value, dict)
and isinstance(value, dict)
):
existing_value.update(value) existing_value.update(value)
value = existing_value value = existing_value

View file

@ -83,9 +83,7 @@ def clear_old_cache_files(max_cache_size: int = 3):
cache_files = list(cache_dir.glob("*.dill")) cache_files = list(cache_dir.glob("*.dill"))
if len(cache_files) > max_cache_size: if len(cache_files) > max_cache_size:
cache_files_sorted_by_mtime = sorted( cache_files_sorted_by_mtime = sorted(cache_files, key=lambda x: x.stat().st_mtime, reverse=True)
cache_files, key=lambda x: x.stat().st_mtime, reverse=True
)
for cache_file in cache_files_sorted_by_mtime[max_cache_size:]: for cache_file in cache_files_sorted_by_mtime[max_cache_size:]:
with contextlib.suppress(OSError): with contextlib.suppress(OSError):

View file

@ -1,4 +1,4 @@
from langflow.services.chat.manager import ChatService from langflow.services.chat.service import ChatService
from langflow.services.factory import ServiceFactory from langflow.services.factory import ServiceFactory

View file

@ -1,20 +1,20 @@
from collections import defaultdict import asyncio
import uuid import uuid
from collections import defaultdict
from typing import Any, Dict, List
import orjson
from fastapi import WebSocket, status from fastapi import WebSocket, status
from starlette.websockets import WebSocketState
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
from langflow.interface.utils import pil_to_base64 from langflow.interface.utils import pil_to_base64
from langflow.services import ServiceType, service_manager
from langflow.services.base import Service from langflow.services.base import Service
from langflow.services.chat.cache import Subject from langflow.services.chat.cache import Subject
from langflow.services.chat.utils import process_graph from langflow.services.chat.utils import process_graph
from loguru import logger from loguru import logger
from starlette.websockets import WebSocketState
from .cache import cache_service from .cache import cache_service
import asyncio
from typing import Any, Dict, List
from langflow.services import service_manager, ServiceType
import orjson
class ChatHistory(Subject): class ChatHistory(Subject):
@ -59,9 +59,7 @@ class ChatService(Service):
"""Send the last chat message to the client.""" """Send the last chat message to the client."""
client_id = self.chat_cache.current_client_id client_id = self.chat_cache.current_client_id
if client_id in self.active_connections: if client_id in self.active_connections:
chat_response = self.chat_history.get_history( chat_response = self.chat_history.get_history(client_id, filter_messages=False)[-1]
client_id, filter_messages=False
)[-1]
if chat_response.is_bot: if chat_response.is_bot:
# Process FileResponse # Process FileResponse
if isinstance(chat_response, FileResponse): if isinstance(chat_response, FileResponse):
@ -88,9 +86,7 @@ class ChatService(Service):
data_type=self.last_cached_object_dict["type"], data_type=self.last_cached_object_dict["type"],
) )
self.chat_history.add_message( self.chat_history.add_message(self.chat_cache.current_client_id, chat_response)
self.chat_cache.current_client_id, chat_response
)
async def connect(self, client_id: str, websocket: WebSocket): async def connect(self, client_id: str, websocket: WebSocket):
self.active_connections[client_id] = websocket self.active_connections[client_id] = websocket
@ -108,7 +104,7 @@ class ChatService(Service):
async def send_json(self, client_id: str, message: ChatMessage): async def send_json(self, client_id: str, message: ChatMessage):
websocket = self.active_connections[client_id] websocket = self.active_connections[client_id]
await websocket.send_json(message.dict()) await websocket.send_json(message.model_dump())
async def close_connection(self, client_id: str, code: int, reason: str): async def close_connection(self, client_id: str, code: int, reason: str):
if websocket := self.active_connections[client_id]: if websocket := self.active_connections[client_id]:
@ -121,9 +117,7 @@ class ChatService(Service):
if "after sending" in str(exc): if "after sending" in str(exc):
logger.error(f"Error closing connection: {exc}") logger.error(f"Error closing connection: {exc}")
async def process_message( async def process_message(self, client_id: str, payload: Dict, langchain_object: Any):
self, client_id: str, payload: Dict, langchain_object: Any
):
# Process the graph data and chat message # Process the graph data and chat message
chat_inputs = payload.pop("inputs", {}) chat_inputs = payload.pop("inputs", {})
chatkey = payload.pop("chatKey", None) chatkey = payload.pop("chatKey", None)
@ -197,7 +191,7 @@ class ChatService(Service):
try: try:
chat_history = self.chat_history.get_history(client_id) chat_history = self.chat_history.get_history(client_id)
# iterate and make BaseModel into dict # iterate and make BaseModel into dict
chat_history = [chat.dict() for chat in chat_history] chat_history = [chat.model_dump() for chat in chat_history]
await websocket.send_json(chat_history) await websocket.send_json(chat_history)
while True: while True:
@ -211,15 +205,11 @@ class ChatService(Service):
continue continue
with self.chat_cache.set_client_id(client_id): with self.chat_cache.set_client_id(client_id):
if langchain_object := self.cache_service.get(client_id).get( if langchain_object := self.cache_service.get(client_id).get("result"):
"result"
):
await self.process_message(client_id, payload, langchain_object) await self.process_message(client_id, payload, langchain_object)
else: else:
raise RuntimeError( raise RuntimeError(f"Could not find a build result for client_id {client_id}")
f"Could not find a build result for client_id {client_id}"
)
except Exception as exc: except Exception as exc:
# Handle any exceptions that might occur # Handle any exceptions that might occur
logger.exception(f"Error handling websocket: {exc}") logger.exception(f"Error handling websocket: {exc}")

View file

@ -15,9 +15,7 @@ async def process_graph(
if langchain_object is None: if langchain_object is None:
# Raise user facing error # Raise user facing error
raise ValueError( raise ValueError("There was an error loading the langchain_object. Please, check all the nodes and try again.")
"There was an error loading the langchain_object. Please, check all the nodes and try again."
)
# Generate result and thought # Generate result and thought
try: try:

View file

@ -1,9 +1,9 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from langflow.services.database.manager import DatabaseService from langflow.services.database.service import DatabaseService
from langflow.services.factory import ServiceFactory from langflow.services.factory import ServiceFactory
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.settings.manager import SettingsService from langflow.services.settings.service import SettingsService
class DatabaseServiceFactory(ServiceFactory): class DatabaseServiceFactory(ServiceFactory):

View file

@ -18,9 +18,7 @@ def get_api_keys(session: Session, user_id: UUID) -> List[ApiKeyRead]:
return [ApiKeyRead.from_orm(api_key) for api_key in api_keys] return [ApiKeyRead.from_orm(api_key) for api_key in api_keys]
def create_api_key( def create_api_key(session: Session, api_key_create: ApiKeyCreate, user_id: UUID) -> UnmaskedApiKeyRead:
session: Session, api_key_create: ApiKeyCreate, user_id: UUID
) -> UnmaskedApiKeyRead:
# Generate a random API key with 32 bytes of randomness # Generate a random API key with 32 bytes of randomness
generated_api_key = f"sk-{secrets.token_urlsafe(32)}" generated_api_key = f"sk-{secrets.token_urlsafe(32)}"

View file

@ -15,6 +15,7 @@ class FlowBase(SQLModelSerializable):
name: str = Field(index=True) name: str = Field(index=True)
description: Optional[str] = Field(index=True, nullable=True, default=None) description: Optional[str] = Field(index=True, nullable=True, default=None)
data: Optional[Dict] = Field(default=None, nullable=True) data: Optional[Dict] = Field(default=None, nullable=True)
is_component: Optional[bool] = Field(default=False, nullable=True)
@field_validator("data") @field_validator("data")
def validate_json(v): def validate_json(v):
@ -35,7 +36,7 @@ class FlowBase(SQLModelSerializable):
class Flow(FlowBase, table=True): class Flow(FlowBase, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON)) data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
user_id: UUID = Field(index=True, foreign_key="user.id") user_id: UUID = Field(index=True, foreign_key="user.id", nullable=True)
user: "User" = Relationship(back_populates="flows") user: "User" = Relationship(back_populates="flows")

View file

@ -3,7 +3,7 @@ from typing import Union
from uuid import UUID from uuid import UUID
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from langflow.services.database.models.user.user import User, UserUpdate from langflow.services.database.models.user.user import User, UserUpdate
from langflow.services.getters import get_session from langflow.services.deps import get_session
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from sqlmodel import Session from sqlmodel import Session
from typing import Optional from typing import Optional
@ -19,9 +19,7 @@ def get_user_by_id(db: Session, id: UUID) -> Union[User, None]:
return db.query(User).filter(User.id == id).first() return db.query(User).filter(User.id == id).first()
def update_user( def update_user(user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session)) -> User:
user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session)
) -> User:
if not user_db: if not user_db:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
@ -37,9 +35,7 @@ def update_user(
changed = True changed = True
if not changed: if not changed:
raise HTTPException( raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update")
status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update"
)
user_db.updated_at = datetime.now(timezone.utc) user_db.updated_at = datetime.now(timezone.utc)
flag_modified(user_db, "updated_at") flag_modified(user_db, "updated_at")

View file

@ -25,6 +25,7 @@ class User(SQLModelSerializable, table=True):
back_populates="user", back_populates="user",
sa_relationship_kwargs={"cascade": "delete"}, sa_relationship_kwargs={"cascade": "delete"},
) )
store_api_key: str = Field(default=None, nullable=True)
flows: list["Flow"] = Relationship(back_populates="user") flows: list["Flow"] = Relationship(back_populates="user")

View file

@ -3,15 +3,17 @@ from typing import TYPE_CHECKING
from langflow.services.base import Service from langflow.services.base import Service
from langflow.services.database.models.user.crud import get_user_by_username from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults from langflow.services.database.utils import Result, TableResults
from langflow.services.getters import get_settings_service from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
from sqlalchemy import inspect from sqlalchemy import inspect
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.exc import OperationalError from sqlalchemy.exc import OperationalError
from sqlmodel import SQLModel, Session, create_engine from sqlmodel import SQLModel, Session, create_engine
from loguru import logger from loguru import logger
from alembic.config import Config from alembic.config import Config
from alembic import command from alembic import command, util
from langflow.services.database import models # noqa from langflow.services.database import models # noqa
import time
if TYPE_CHECKING: if TYPE_CHECKING:
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
@ -32,10 +34,7 @@ class DatabaseService(Service):
def _create_engine(self) -> "Engine": def _create_engine(self) -> "Engine":
"""Create the engine for the database.""" """Create the engine for the database."""
settings_service = get_settings_service() settings_service = get_settings_service()
if ( if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"):
settings_service.settings.DATABASE_URL
and settings_service.settings.DATABASE_URL.startswith("sqlite")
):
connect_args = {"check_same_thread": False} connect_args = {"check_same_thread": False}
else: else:
connect_args = {} connect_args = {}
@ -47,9 +46,7 @@ class DatabaseService(Service):
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None: # If an exception has been raised if exc_type is not None: # If an exception has been raised
logger.error( logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}")
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
)
self._session.rollback() self._session.rollback()
else: else:
self._session.commit() self._session.commit()
@ -97,9 +94,7 @@ class DatabaseService(Service):
expected_columns = list(model.model_fields.keys()) expected_columns = list(model.model_fields.keys())
try: try:
available_columns = [ available_columns = [col["name"] for col in inspector.get_columns(table)]
col["name"] for col in inspector.get_columns(table)
]
except sa.exc.NoSuchTableError: except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table}") logger.error(f"Missing table: {table}")
return False return False
@ -148,6 +143,21 @@ class DatabaseService(Service):
alembic_cfg = Config() alembic_cfg = Config()
alembic_cfg.set_main_option("script_location", str(self.script_location)) alembic_cfg.set_main_option("script_location", str(self.script_location))
alembic_cfg.set_main_option("sqlalchemy.url", self.database_url) alembic_cfg.set_main_option("sqlalchemy.url", self.database_url)
try:
command.check(alembic_cfg)
except Exception as exc:
if isinstance(exc, util.exc.CommandError) or isinstance(exc, util.exc.AutogenerateDiffsDetected):
command.upgrade(alembic_cfg, "head")
# We should check the schema health after running migrations
try:
command.check(alembic_cfg)
except util.exc.AutogenerateDiffsDetected:
# downgrade to base and upgrade again
logger.warning("Autogenerate diffs detected, downgrading and upgrading")
command.downgrade(alembic_cfg, "-1")
# wait for the database to be ready
time.sleep(5)
command.upgrade(alembic_cfg, "head") command.upgrade(alembic_cfg, "head")
def run_migrations_test(self): def run_migrations_test(self):
@ -155,10 +165,7 @@ class DatabaseService(Service):
# We will check that all models are in the database # We will check that all models are in the database
# and that the database is up to date with all columns # and that the database is up to date with all columns
sql_models = [models.Flow, models.User, models.ApiKey] sql_models = [models.Flow, models.User, models.ApiKey]
return [ return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models]
TableResults(sql_model.__tablename__, self.check_table(sql_model))
for sql_model in sql_models
]
def check_table(self, model): def check_table(self, model):
results = [] results = []
@ -166,9 +173,7 @@ class DatabaseService(Service):
table_name = model.__tablename__ table_name = model.__tablename__
expected_columns = list(model.__fields__.keys()) expected_columns = list(model.__fields__.keys())
try: try:
available_columns = [ available_columns = [col["name"] for col in inspector.get_columns(table_name)]
col["name"] for col in inspector.get_columns(table_name)
]
results.append(Result(name=table_name, type="table", success=True)) results.append(Result(name=table_name, type="table", success=True))
except sa.exc.NoSuchTableError: except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table_name}") logger.error(f"Missing table: {table_name}")
@ -199,9 +204,7 @@ class DatabaseService(Service):
try: try:
table.create(self.engine, checkfirst=True) table.create(self.engine, checkfirst=True)
except OperationalError as oe: except OperationalError as oe:
logger.warning( logger.warning(f"Table {table} already exists, skipping. Exception: {oe}")
f"Table {table} already exists, skipping. Exception: {oe}"
)
except Exception as exc: except Exception as exc:
logger.error(f"Error creating table {table}: {exc}") logger.error(f"Error creating table {table}: {exc}")
raise RuntimeError(f"Error creating table {table}") from exc raise RuntimeError(f"Error creating table {table}") from exc
@ -213,9 +216,7 @@ class DatabaseService(Service):
if table not in table_names: if table not in table_names:
logger.error("Something went wrong creating the database and tables.") logger.error("Something went wrong creating the database and tables.")
logger.error("Please check your database settings.") logger.error("Please check your database settings.")
raise RuntimeError( raise RuntimeError("Something went wrong creating the database and tables.")
"Something went wrong creating the database and tables."
)
logger.debug("Database and tables created successfully") logger.debug("Database and tables created successfully")
@ -225,14 +226,8 @@ class DatabaseService(Service):
settings_service = get_settings_service() settings_service = get_settings_service()
# remove the default superuser if auto_login is enabled # remove the default superuser if auto_login is enabled
# using the SUPERUSER to get the user # using the SUPERUSER to get the user
if settings_service.auth_settings.AUTO_LOGIN:
logger.debug("Removing default superuser")
username = settings_service.auth_settings.SUPERUSER
with Session(self.engine) as session: with Session(self.engine) as session:
user = get_user_by_username(session, username) teardown_superuser(settings_service, session)
session.delete(user)
session.commit()
logger.debug("Default superuser removed")
except Exception as exc: except Exception as exc:
logger.error(f"Error tearing down database: {exc}") logger.error(f"Error tearing down database: {exc}")

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