Merge remote-tracking branch 'origin/dev' into fix_toolkits

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-06-02 12:56:02 -03:00
commit 9517ffe3b3
35 changed files with 605 additions and 362 deletions

View file

@ -1,2 +1,6 @@
#!/bin/sh
added_files=$(git diff --name-only --cached --diff-filter=d)
make format make format
git add ${added_files}

View file

@ -59,7 +59,7 @@ lcserve_push:
make build_frontend make build_frontend
@version=$$(poetry version --short); \ @version=$$(poetry version --short); \
lc-serve push --app langflow.lcserve:app --app-dir . \ lc-serve push --app langflow.lcserve:app --app-dir . \
--image-name langflow --image-tag $${version} --verbose --image-name langflow --image-tag $${version} --verbose --public
lcserve_deploy: lcserve_deploy:
@:$(if $(uses),,$(error `uses` is not set. Please run `make uses=... lcserve_deploy`)) @:$(if $(uses),,$(error `uses` is not set. Please run `make uses=... lcserve_deploy`))

101
poetry.lock generated
View file

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
[[package]] [[package]]
name = "aiofiles" name = "aiofiles"
@ -862,31 +862,31 @@ toml = ["tomli"]
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "40.0.2" version = "41.0.0"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.7"
files = [ files = [
{file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b"}, {file = "cryptography-41.0.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8"},
{file = "cryptography-40.0.2-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440"}, {file = "cryptography-41.0.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0"},
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d"}, {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d"},
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288"}, {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46"},
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2"}, {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237"},
{file = "cryptography-40.0.2-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b"}, {file = "cryptography-41.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4"},
{file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"}, {file = "cryptography-41.0.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75"},
{file = "cryptography-40.0.2-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c"}, {file = "cryptography-41.0.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d"},
{file = "cryptography-40.0.2-cp36-abi3-win32.whl", hash = "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9"}, {file = "cryptography-41.0.0-cp37-abi3-win32.whl", hash = "sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928"},
{file = "cryptography-40.0.2-cp36-abi3-win_amd64.whl", hash = "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b"}, {file = "cryptography-41.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be"},
{file = "cryptography-40.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b"}, {file = "cryptography-41.0.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5"},
{file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e"}, {file = "cryptography-41.0.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb"},
{file = "cryptography-40.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a"}, {file = "cryptography-41.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be"},
{file = "cryptography-40.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958"}, {file = "cryptography-41.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9"},
{file = "cryptography-40.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b"}, {file = "cryptography-41.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2"},
{file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636"}, {file = "cryptography-41.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d"},
{file = "cryptography-40.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e"}, {file = "cryptography-41.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895"},
{file = "cryptography-40.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404"}, {file = "cryptography-41.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55"},
{file = "cryptography-40.0.2.tar.gz", hash = "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99"}, {file = "cryptography-41.0.0.tar.gz", hash = "sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78"},
] ]
[package.dependencies] [package.dependencies]
@ -895,23 +895,23 @@ cffi = ">=1.12"
[package.extras] [package.extras]
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
pep8test = ["black", "check-manifest", "mypy", "ruff"] nox = ["nox"]
sdist = ["setuptools-rust (>=0.11.4)"] pep8test = ["black", "check-sdist", "mypy", "ruff"]
sdist = ["build"]
ssh = ["bcrypt (>=3.1.5)"] ssh = ["bcrypt (>=3.1.5)"]
test = ["iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"] test-randomorder = ["pytest-randomly"]
tox = ["tox"]
[[package]] [[package]]
name = "ctransformers" name = "ctransformers"
version = "0.2.2" version = "0.2.3"
description = "Python bindings for the Transformer models implemented in C/C++ using GGML library." description = "Python bindings for the Transformer models implemented in C/C++ using GGML library."
category = "main" category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "ctransformers-0.2.2-py3-none-any.whl", hash = "sha256:bf682dd0293dd87911c9a9a1169a4873ff55baebc16d465c6029c77f11b18cf6"}, {file = "ctransformers-0.2.3-py3-none-any.whl", hash = "sha256:5043b0808839cd34b0c7d1b897b81ac7e3d4778674b6226aef18b622be4b75c9"},
{file = "ctransformers-0.2.2.tar.gz", hash = "sha256:1fc36b3fde36d9fd3cb69e48993315bb1f5f54ae552720eb909dc4b3a131c743"}, {file = "ctransformers-0.2.3.tar.gz", hash = "sha256:87fc9966b62fbdadb01b91b6373287e1af50e176b5dd409f4f2d1ff0fa9f7c99"},
] ]
[package.dependencies] [package.dependencies]
@ -1096,7 +1096,9 @@ files = [
{file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2707096d6df4321044fcde2c9f04da632d11a8be60957fd09d49a42fae71a29"}, {file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2707096d6df4321044fcde2c9f04da632d11a8be60957fd09d49a42fae71a29"},
{file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b27df1b70ae74d2c88efb5ffca8490954fdc678099509a9c4404ca30acc53426"}, {file = "duckdb-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b27df1b70ae74d2c88efb5ffca8490954fdc678099509a9c4404ca30acc53426"},
{file = "duckdb-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75a97c800271b52dd0f37696d074c50576dcb4b2750b6115932a98696a268070"}, {file = "duckdb-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75a97c800271b52dd0f37696d074c50576dcb4b2750b6115932a98696a268070"},
{file = "duckdb-0.8.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:804cac261a5e016506a6d67838a65d19b06a237f7949f1704f0e800eb708286a"},
{file = "duckdb-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b9abca7fa6713e1d031c18485343b4de99742c7e1b85c10718aa2f31a4e2c6"}, {file = "duckdb-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b9abca7fa6713e1d031c18485343b4de99742c7e1b85c10718aa2f31a4e2c6"},
{file = "duckdb-0.8.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:51aa6d606d49072abcfeb3be209eb559ac94c1b5e70f58ac3adbb94aca9cd69f"},
{file = "duckdb-0.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7c8dc769aaf2be0a1c57995ca657e5b92c1c56fc8437edb720ca6cab571adf14"}, {file = "duckdb-0.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7c8dc769aaf2be0a1c57995ca657e5b92c1c56fc8437edb720ca6cab571adf14"},
{file = "duckdb-0.8.0-cp311-cp311-win32.whl", hash = "sha256:c4207d18b42387c4a035846d8878eb967070198be8ac26fd77797ce320d1a400"}, {file = "duckdb-0.8.0-cp311-cp311-win32.whl", hash = "sha256:c4207d18b42387c4a035846d8878eb967070198be8ac26fd77797ce320d1a400"},
{file = "duckdb-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:0c392257547c20794c3072fcbca99a49ef0a49974005d755e93893e2b4875267"}, {file = "duckdb-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:0c392257547c20794c3072fcbca99a49ef0a49974005d755e93893e2b4875267"},
@ -1192,6 +1194,41 @@ files = [
[package.extras] [package.extras]
tests = ["asttokens", "littleutils", "pytest", "rich"] tests = ["asttokens", "littleutils", "pytest", "rich"]
[[package]]
name = "faiss-cpu"
version = "1.7.4"
description = "A library for efficient similarity search and clustering of dense vectors."
category = "main"
optional = false
python-versions = "*"
files = [
{file = "faiss-cpu-1.7.4.tar.gz", hash = "sha256:265dc31b0c079bf4433303bf6010f73922490adff9188b915e2d3f5e9c82dd0a"},
{file = "faiss_cpu-1.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50d4ebe7f1869483751c558558504f818980292a9b55be36f9a1ee1009d9a686"},
{file = "faiss_cpu-1.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b1db7fae7bd8312aeedd0c41536bcd19a6e297229e1dce526bde3a73ab8c0b5"},
{file = "faiss_cpu-1.7.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17b7fa7194a228a84929d9e6619d0e7dbf00cc0f717e3462253766f5e3d07de8"},
{file = "faiss_cpu-1.7.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dca531952a2e3eac56f479ff22951af4715ee44788a3fe991d208d766d3f95f3"},
{file = "faiss_cpu-1.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:7173081d605e74766f950f2e3d6568a6f00c53f32fd9318063e96728c6c62821"},
{file = "faiss_cpu-1.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0bbd6f55d7940cc0692f79e32a58c66106c3c950cee2341b05722de9da23ea3"},
{file = "faiss_cpu-1.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13c14280376100f143767d0efe47dcb32618f69e62bbd3ea5cd38c2e1755926"},
{file = "faiss_cpu-1.7.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c521cb8462f3b00c0c7dfb11caff492bb67816528b947be28a3b76373952c41d"},
{file = "faiss_cpu-1.7.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afdd9fe1141117fed85961fd36ee627c83fc3b9fd47bafb52d3c849cc2f088b7"},
{file = "faiss_cpu-1.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:2ff7f57889ea31d945e3b87275be3cad5d55b6261a4e3f51c7aba304d76b81fb"},
{file = "faiss_cpu-1.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eeaf92f27d76249fb53c1adafe617b0f217ab65837acf7b4ec818511caf6e3d8"},
{file = "faiss_cpu-1.7.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:102b1bd763e9b0c281ac312590af3eaf1c8b663ccbc1145821fe6a9f92b8eaaf"},
{file = "faiss_cpu-1.7.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5512da6707c967310c46ff712b00418b7ae28e93cb609726136e826e9f2f14fa"},
{file = "faiss_cpu-1.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0c2e5b9d8c28c99f990e87379d5bbcc6c914da91ebb4250166864fd12db5755b"},
{file = "faiss_cpu-1.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43f67f325393145d360171cd98786fcea6120ce50397319afd3bb78be409fb8a"},
{file = "faiss_cpu-1.7.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6a4e4af194b8fce74c4b770cad67ad1dd1b4673677fc169723e4c50ba5bd97a8"},
{file = "faiss_cpu-1.7.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31bfb7b9cffc36897ae02a983e04c09fe3b8c053110a287134751a115334a1df"},
{file = "faiss_cpu-1.7.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52d7de96abef2340c0d373c1f5cbc78026a3cebb0f8f3a5920920a00210ead1f"},
{file = "faiss_cpu-1.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:699feef85b23c2c729d794e26ca69bebc0bee920d676028c06fd0e0becc15c7e"},
{file = "faiss_cpu-1.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:559a0133f5ed44422acb09ee1ac0acffd90c6666d1bc0d671c18f6e93ad603e2"},
{file = "faiss_cpu-1.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1d71539fe3dc0f1bed41ef954ca701678776f231046bf0ca22ccea5cf5bef6"},
{file = "faiss_cpu-1.7.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12d45e0157024eb3249842163162983a1ac8b458f1a8b17bbf86f01be4585a99"},
{file = "faiss_cpu-1.7.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f0eab359e066d32c874f51a7d4bf6440edeec068b7fe47e6d803c73605a8b4c"},
{file = "faiss_cpu-1.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:98459ceeeb735b9df1a5b94572106ffe0a6ce740eb7e4626715dd218657bb4dc"},
]
[[package]] [[package]]
name = "fake-useragent" name = "fake-useragent"
version = "1.1.3" version = "1.1.3"
@ -2374,13 +2411,13 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
[[package]] [[package]]
name = "langchain-serve" name = "langchain-serve"
version = "0.0.38" version = "0.0.40"
description = "Langchain Serve - serve your langchain apps on Jina AI Cloud." description = "Langchain Serve - serve your langchain apps on Jina AI Cloud."
category = "main" category = "main"
optional = true optional = true
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "langchain-serve-0.0.38.tar.gz", hash = "sha256:649b8e26eebe6b33960c081b388fb7118acbfdc00f97dd935a580ab88aca53d6"}, {file = "langchain-serve-0.0.40.tar.gz", hash = "sha256:c60b173fcf0b682fbb70d34e8f485ce168e2229f55cb5c4ffbc26a5206af1c06"},
] ]
[package.dependencies] [package.dependencies]
@ -6178,4 +6215,4 @@ deploy = ["langchain-serve"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.12" python-versions = ">=3.9,<3.12"
content-hash = "9ce165d2decf2190d7ce69be608872b3ed9abe705a276045623706d01665754b" content-hash = "0c15df0da26611ffa09238660bc024b485ffb5f1b06890808751947a8467ae58"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.0.79" version = "0.0.80"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -49,7 +49,7 @@ psycopg2-binary = "^2.9.6"
pyarrow = "^11.0.0" pyarrow = "^11.0.0"
tiktoken = "^0.3.3" tiktoken = "^0.3.3"
wikipedia = "^1.4.0" wikipedia = "^1.4.0"
langchain-serve = { version = "^0.0.38", optional = true } langchain-serve = { version = ">0.0.39", optional = true }
qdrant-client = "^1.2.0" qdrant-client = "^1.2.0"
websockets = "^11.0.3" websockets = "^11.0.3"
weaviate-client = "^3.19.2" weaviate-client = "^3.19.2"
@ -57,6 +57,7 @@ jina = "3.15.2"
sentence-transformers = "^2.2.2" sentence-transformers = "^2.2.2"
ctransformers = "^0.2.2" ctransformers = "^0.2.2"
cohere = "^4.6.0" cohere = "^4.6.0"
faiss-cpu = "^1.7.4"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]

View file

@ -9,7 +9,7 @@ from langflow.api.base import (
PromptValidationResponse, PromptValidationResponse,
validate_prompt, validate_prompt,
) )
from langflow.graph.nodes import VectorStoreNode from langflow.graph.vertex.types import VectorStoreVertex
from langflow.interface.run import build_graph from langflow.interface.run import build_graph
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.validate import validate_code from langflow.utils.validate import validate_code
@ -49,7 +49,7 @@ def post_validate_node(node_id: str, data: dict):
node = graph.get_node(node_id) node = graph.get_node(node_id)
if node is None: if node is None:
raise ValueError(f"Node {node_id} not found") raise ValueError(f"Node {node_id} not found")
if not isinstance(node, VectorStoreNode): if not isinstance(node, VectorStoreVertex):
node.build() node.build()
return json.dumps({"valid": True, "params": str(node._built_object_repr())}) return json.dumps({"valid": True, "params": str(node._built_object_repr())})
except Exception as e: except Exception as e:

View file

@ -80,7 +80,7 @@ tools:
- Calculator - Calculator
- Serper Search - Serper Search
- Tool - Tool
- PythonFunction - PythonFunctionTool
- JsonSpec - JsonSpec
- News API - News API
- TMDB API - TMDB API
@ -119,6 +119,7 @@ vectorstores:
- Chroma - Chroma
- Qdrant - Qdrant
- Weaviate - Weaviate
- FAISS
wrappers: wrappers:
- RequestsWrapper - RequestsWrapper
# - ChatPromptTemplate # - ChatPromptTemplate

View file

@ -4,7 +4,7 @@ from langflow.template import frontend_node
CUSTOM_NODES = { CUSTOM_NODES = {
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()}, "prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
"tools": { "tools": {
"PythonFunction": frontend_node.tools.PythonFunctionNode(), "PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"Tool": frontend_node.tools.ToolNode(), "Tool": frontend_node.tools.ToolNode(),
}, },
"agents": { "agents": {

View file

@ -1,4 +1,35 @@
from langflow.graph.base import Edge, Node from langflow.graph.edge.base import Edge
from langflow.graph.graph import Graph from langflow.graph.graph.base import Graph
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import (
AgentVertex,
ChainVertex,
DocumentLoaderVertex,
EmbeddingVertex,
LLMVertex,
MemoryVertex,
PromptVertex,
TextSplitterVertex,
ToolVertex,
ToolkitVertex,
VectorStoreVertex,
WrapperVertex,
)
__all__ = ["Graph", "Node", "Edge"] __all__ = [
"Graph",
"Vertex",
"Edge",
"AgentVertex",
"ChainVertex",
"DocumentLoaderVertex",
"EmbeddingVertex",
"LLMVertex",
"MemoryVertex",
"PromptVertex",
"TextSplitterVertex",
"ToolVertex",
"ToolkitVertex",
"VectorStoreVertex",
"WrapperVertex",
]

View file

@ -0,0 +1,52 @@
from langflow.utils.logger import logger
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
class Edge:
def __init__(self, source: "Vertex", target: "Vertex"):
self.source: "Vertex" = source
self.target: "Vertex" = target
self.validate_edge()
def validate_edge(self) -> None:
# Validate that the outputs of the source node are valid inputs
# for the target node
self.source_types = self.source.output
self.target_reqs = self.target.required_inputs + self.target.optional_inputs
# Both lists contain strings and sometimes a string contains the value we are
# 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
self.valid = any(
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
self.matched_type = next(
(
output
for output in self.source_types
for target_req in self.target_reqs
if output in target_req
),
None,
)
no_matched_type = self.matched_type is None
if no_matched_type:
logger.debug(self.source_types)
logger.debug(self.target_reqs)
if no_matched_type:
raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
f"has no matched type"
)
def __repr__(self) -> str:
return (
f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}"
f", matched_type={self.matched_type})"
)

View file

@ -1,38 +1,20 @@
from typing import Dict, List, Type, Union from typing import Dict, List, Type, Union
from langflow.graph.base import Edge, Node from langflow.graph.edge.base import Edge
from langflow.graph.nodes import ( from langflow.graph.graph.constants import VERTEX_TYPE_MAP
AgentNode, from langflow.graph.vertex.base import Vertex
ChainNode, from langflow.graph.vertex.types import (
DocumentLoaderNode, FileToolVertex,
EmbeddingNode, LLMVertex,
FileToolNode, ToolkitVertex,
LLMNode,
MemoryNode,
PromptNode,
TextSplitterNode,
ToolkitNode,
ToolNode,
VectorStoreNode,
WrapperNode,
) )
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator
from langflow.interface.tools.constants import FILE_TOOLS from langflow.interface.tools.constants import FILE_TOOLS
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils import payload from langflow.utils import payload
class Graph: class Graph:
"""A class representing a graph of nodes and edges."""
def __init__( def __init__(
self, self,
nodes: List[Dict[str, Union[str, Dict[str, Union[str, List[str]]]]]], nodes: List[Dict[str, Union[str, Dict[str, Union[str, List[str]]]]]],
@ -43,7 +25,8 @@ class Graph:
self._build_graph() self._build_graph()
def _build_graph(self) -> None: def _build_graph(self) -> None:
self.nodes = self._build_nodes() """Builds the graph from the nodes and edges."""
self.nodes = self._build_vertices()
self.edges = self._build_edges() self.edges = self._build_edges()
for edge in self.edges: for edge in self.edges:
edge.source.add_edge(edge) edge.source.add_edge(edge)
@ -51,17 +34,25 @@ class Graph:
# This is a hack to make sure that the LLM node is sent to # This is a hack to make sure that the LLM node is sent to
# the toolkit node # the toolkit node
self._build_node_params()
# remove invalid nodes
self._remove_invalid_nodes()
def _build_node_params(self) -> None:
"""Identifies and handles the LLM node within the graph."""
llm_node = None llm_node = None
for node in self.nodes: for node in self.nodes:
node._build_params() node._build_params()
if isinstance(node, LLMVertex):
if isinstance(node, LLMNode):
llm_node = node llm_node = node
if llm_node:
for node in self.nodes: for node in self.nodes:
if isinstance(node, ToolkitNode): if isinstance(node, ToolkitVertex):
node.params["llm"] = llm_node node.params["llm"] = llm_node
# remove invalid nodes
def _remove_invalid_nodes(self) -> None:
"""Removes invalid nodes from the graph."""
self.nodes = [ self.nodes = [
node node
for node in self.nodes for node in self.nodes
@ -69,28 +60,33 @@ class Graph:
or (len(self.nodes) == 1 and len(self.edges) == 0) or (len(self.nodes) == 1 and len(self.edges) == 0)
] ]
def _validate_node(self, node: Node) -> bool: def _validate_node(self, node: Vertex) -> bool:
"""Validates a node."""
# All nodes that do not have edges are invalid # All nodes that do not have edges are invalid
return len(node.edges) > 0 return len(node.edges) > 0
def get_node(self, node_id: str) -> Union[None, Node]: def get_node(self, node_id: str) -> Union[None, Vertex]:
"""Returns a node by id."""
return next((node for node in self.nodes if node.id == node_id), None) return next((node for node in self.nodes if node.id == node_id), None)
def get_nodes_with_target(self, node: Node) -> List[Node]: def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
connected_nodes: List[Node] = [ """Returns the nodes connected to a node."""
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) -> List[Node]: def build(self) -> List[Vertex]:
"""Builds the graph."""
# Get root node # Get root node
root_node = payload.get_root_node(self) root_node = payload.get_root_node(self)
if root_node is None: if root_node is None:
raise ValueError("No root node found") raise ValueError("No root node found")
return root_node.build() return root_node.build()
def get_node_neighbors(self, node: Node) -> Dict[Node, int]: def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
neighbors: Dict[Node, int] = {} """Returns the neighbors of a node."""
neighbors: Dict[Vertex, int] = {}
for edge in self.edges: for edge in self.edges:
if edge.source == node: if edge.source == node:
neighbor = edge.target neighbor = edge.target
@ -105,6 +101,7 @@ class Graph:
return neighbors return neighbors
def _build_edges(self) -> List[Edge]: def _build_edges(self) -> List[Edge]:
"""Builds the edges of the graph."""
# Edge takes two nodes as arguments, so we need to build the nodes first # Edge takes two nodes as arguments, so we need to build the nodes first
# and then build the edges # and then build the edges
# if we can't find a node, we raise an error # if we can't find a node, we raise an error
@ -120,43 +117,31 @@ class Graph:
edges.append(Edge(source, target)) edges.append(Edge(source, target))
return edges return edges
def _get_node_class(self, node_type: str, node_lc_type: str) -> Type[Node]: def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
node_type_map: Dict[str, Type[Node]] = { """Returns the node class based on the node type."""
**{t: PromptNode for t in prompt_creator.to_list()},
**{t: AgentNode for t in agent_creator.to_list()},
**{t: ChainNode for t in chain_creator.to_list()},
**{t: ToolNode for t in tool_creator.to_list()},
**{t: ToolkitNode for t in toolkits_creator.to_list()},
**{t: WrapperNode for t in wrapper_creator.to_list()},
**{t: LLMNode for t in llm_creator.to_list()},
**{t: MemoryNode for t in memory_creator.to_list()},
**{t: EmbeddingNode for t in embedding_creator.to_list()},
**{t: VectorStoreNode for t in vectorstore_creator.to_list()},
**{t: DocumentLoaderNode for t in documentloader_creator.to_list()},
**{t: TextSplitterNode for t in textsplitter_creator.to_list()},
}
if node_type in FILE_TOOLS: if node_type in FILE_TOOLS:
return FileToolNode return FileToolVertex
if node_type in node_type_map: if node_type in VERTEX_TYPE_MAP:
return node_type_map[node_type] return VERTEX_TYPE_MAP[node_type]
if node_lc_type in node_type_map: return (
return node_type_map[node_lc_type] VERTEX_TYPE_MAP[node_lc_type] if node_lc_type in VERTEX_TYPE_MAP else Vertex
return Node )
def _build_nodes(self) -> List[Node]: def _build_vertices(self) -> List[Vertex]:
nodes: List[Node] = [] """Builds the vertices of the graph."""
nodes: List[Vertex] = []
for node in self._nodes: for node in self._nodes:
node_data = node["data"] node_data = node["data"]
node_type: str = node_data["type"] # type: ignore node_type: str = node_data["type"] # type: ignore
node_lc_type: str = node_data["node"]["template"]["_type"] # type: ignore node_lc_type: str = node_data["node"]["template"]["_type"] # type: ignore
NodeClass = self._get_node_class(node_type, node_lc_type) VertexClass = self._get_vertex_class(node_type, node_lc_type)
nodes.append(NodeClass(node)) nodes.append(VertexClass(node))
return nodes return nodes
def get_children_by_node_type(self, node: Node, node_type: str) -> List[Node]: def get_children_by_node_type(self, node: Vertex, node_type: str) -> List[Vertex]:
"""Returns the children of a node based on the node type."""
children = [] children = []
node_types = [node.data["type"]] node_types = [node.data["type"]]
if "node" in node.data: if "node" in node.data:

View file

@ -0,0 +1,49 @@
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import (
AgentVertex,
ChainVertex,
DocumentLoaderVertex,
EmbeddingVertex,
LLMVertex,
MemoryVertex,
PromptVertex,
TextSplitterVertex,
ToolVertex,
ToolkitVertex,
VectorStoreVertex,
WrapperVertex,
)
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator
from typing import Dict, Type
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
**{t: PromptVertex for t in prompt_creator.to_list()},
**{t: AgentVertex for t in agent_creator.to_list()},
**{t: ChainVertex for t in chain_creator.to_list()},
**{t: ToolVertex for t in tool_creator.to_list()},
**{t: ToolkitVertex for t in toolkits_creator.to_list()},
**{t: WrapperVertex for t in wrapper_creator.to_list()},
**{t: LLMVertex for t in llm_creator.to_list()},
**{t: MemoryVertex for t in memory_creator.to_list()},
**{t: EmbeddingVertex for t in embedding_creator.to_list()},
**{t: VectorStoreVertex for t in vectorstore_creator.to_list()},
**{t: DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: TextSplitterVertex for t in textsplitter_creator.to_list()},
}

View file

@ -1,27 +1,27 @@
# Description: Graph class for building a graph of nodes and edges
# Insights:
# - Defer prompts building to the last moment or when they have all the tools
# - Build each inner agent first, then build the outer agent
import contextlib
import inspect
import types
import warnings
from typing import Any, Dict, List, Optional
from langflow.cache import base as cache_utils from langflow.cache import base as cache_utils
from langflow.graph.constants import DIRECT_TYPES from langflow.graph.vertex.constants import DIRECT_TYPES
from langflow.interface import loading from langflow.interface import loading
from langflow.interface.listing import ALL_TYPES_DICT from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import sync_to_async from langflow.utils.util import sync_to_async
class Node: import contextlib
import inspect
import types
import warnings
from typing import Any, Dict, List, Optional
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from langflow.graph.edge.base import Edge
class Vertex:
def __init__(self, data: Dict, base_type: Optional[str] = None) -> None: def __init__(self, data: Dict, base_type: Optional[str] = None) -> None:
self.id: str = data["id"] self.id: str = data["id"]
self._data = data self._data = data
self.edges: List[Edge] = [] self.edges: List["Edge"] = []
self.base_type: Optional[str] = base_type self.base_type: Optional[str] = base_type
self._parse_data() self._parse_data()
self._built_object = None self._built_object = None
@ -48,12 +48,12 @@ class Node:
] ]
template_dict = self.data["node"]["template"] template_dict = self.data["node"]["template"]
self.node_type = ( self.vertex_type = (
self.data["type"] if "Tool" not in self.output else template_dict["_type"] self.data["type"] if "Tool" not in self.output else template_dict["_type"]
) )
if self.base_type is None: if self.base_type is None:
for base_type, value in ALL_TYPES_DICT.items(): for base_type, value in ALL_TYPES_DICT.items():
if self.node_type in value: if self.vertex_type in value:
self.base_type = base_type self.base_type = base_type
break break
@ -113,7 +113,7 @@ class Node:
if value["required"] and not edges: if value["required"] and not edges:
# If a required parameter is not found, raise an error # If a required parameter is not found, raise an error
raise ValueError( raise ValueError(
f"Required input {key} for module {self.node_type} not found" f"Required input {key} for module {self.vertex_type} not found"
) )
elif value["list"]: elif value["list"]:
# If this is a list parameter, append all sources to a list # If this is a list parameter, append all sources to a list
@ -128,7 +128,7 @@ class Node:
# so we need to check if value has value # so we need to check if value has value
new_value = value.get("value") new_value = value.get("value")
if new_value is None: if new_value is None:
warnings.warn(f"Value for {key} in {self.node_type} is None. ") warnings.warn(f"Value for {key} in {self.vertex_type} is None. ")
if value.get("type") == "int": if value.get("type") == "int":
with contextlib.suppress(TypeError, ValueError): with contextlib.suppress(TypeError, ValueError):
new_value = int(new_value) # type: ignore new_value = int(new_value) # type: ignore
@ -148,12 +148,12 @@ class Node:
# and continue # and continue
# Another aspect is that the node_type is the class that we need to import # Another aspect is that the node_type is the class that we need to import
# and instantiate with these built params # and instantiate with these built params
logger.debug(f"Building {self.node_type}") logger.debug(f"Building {self.vertex_type}")
# Build each node in the params dict # Build each node in the params dict
for key, value in self.params.copy().items(): for key, value in self.params.copy().items():
# Check if Node or list of Nodes and not self # Check if Node or list of Nodes and not self
# to avoid recursion # to avoid recursion
if isinstance(value, Node): if isinstance(value, Vertex):
if value == self: if value == self:
del self.params[key] del self.params[key]
continue continue
@ -183,7 +183,7 @@ class Node:
self.params[key] = result self.params[key] = result
elif isinstance(value, list) and all( elif isinstance(value, list) and all(
isinstance(node, Node) for node in value isinstance(node, Vertex) for node in value
): ):
self.params[key] = [] self.params[key] = []
for node in value: for node in value:
@ -199,17 +199,17 @@ class Node:
try: try:
self._built_object = loading.instantiate_class( self._built_object = loading.instantiate_class(
node_type=self.node_type, node_type=self.vertex_type,
base_type=self.base_type, base_type=self.base_type,
params=self.params, params=self.params,
) )
except Exception as exc: except Exception as exc:
raise ValueError( raise ValueError(
f"Error building node {self.node_type}: {str(exc)}" f"Error building node {self.vertex_type}: {str(exc)}"
) from exc ) from exc
if self._built_object is None: if self._built_object is None:
raise ValueError(f"Node type {self.node_type} not found") raise ValueError(f"Node type {self.vertex_type} not found")
self._built = True self._built = True
@ -226,57 +226,10 @@ class Node:
return f"Node(id={self.id}, data={self.data})" return f"Node(id={self.id}, data={self.data})"
def __eq__(self, __o: object) -> bool: def __eq__(self, __o: object) -> bool:
return self.id == __o.id if isinstance(__o, Node) else False return self.id == __o.id if isinstance(__o, Vertex) else False
def __hash__(self) -> int: def __hash__(self) -> int:
return id(self) return id(self)
def _built_object_repr(self): def _built_object_repr(self):
return repr(self._built_object) return repr(self._built_object)
class Edge:
def __init__(self, source: "Node", target: "Node"):
self.source: "Node" = source
self.target: "Node" = target
self.validate_edge()
def validate_edge(self) -> None:
# Validate that the outputs of the source node are valid inputs
# for the target node
self.source_types = self.source.output
self.target_reqs = self.target.required_inputs + self.target.optional_inputs
# Both lists contain strings and sometimes a string contains the value we are
# 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
self.valid = any(
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
self.matched_type = next(
(
output
for output in self.source_types
for target_req in self.target_reqs
if output in target_req
),
None,
)
no_matched_type = self.matched_type is None
if no_matched_type:
logger.debug(self.source_types)
logger.debug(self.target_reqs)
if no_matched_type:
raise ValueError(
f"Edge between {self.source.node_type} and {self.target.node_type} "
f"has no matched type"
)
def __repr__(self) -> str:
return (
f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}"
f", matched_type={self.matched_type})"
)

View file

@ -1,15 +1,10 @@
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from langflow.graph.base import Node from langflow.graph.vertex.base import Vertex
from langflow.graph.utils import extract_input_variables_from_prompt, flatten_list from langflow.graph.utils import extract_input_variables_from_prompt
class ToolkitNode(Node): class AgentVertex(Vertex):
def __init__(self, data: Dict):
super().__init__(data, base_type="toolkits")
class AgentNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="agents") super().__init__(data, base_type="agents")
@ -19,9 +14,9 @@ class AgentNode(Node):
def _set_tools_and_chains(self) -> None: def _set_tools_and_chains(self) -> None:
for edge in self.edges: for edge in self.edges:
source_node = edge.source source_node = edge.source
if isinstance(source_node, (ToolNode, ToolkitNode)): if isinstance(source_node, ToolVertex):
self.tools.append(source_node) self.tools.append(source_node)
elif isinstance(source_node, ChainNode): elif isinstance(source_node, ChainVertex):
self.chains.append(source_node) self.chains.append(source_node)
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
@ -40,19 +35,19 @@ class AgentNode(Node):
return self._built_object return self._built_object
class ToolNode(Node): class ToolVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="tools") super().__init__(data, base_type="tools")
class PromptNode(Node): class PromptVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="prompts") super().__init__(data, base_type="prompts")
def build( def build(
self, self,
force: bool = False, force: bool = False,
tools: Optional[List[Union[ToolNode, ToolkitNode]]] = None, tools: Optional[Union[List[Vertex], List[ToolVertex]]] = None,
) -> Any: ) -> Any:
if not self._built or force: if not self._built or force:
if ( if (
@ -61,7 +56,7 @@ class PromptNode(Node):
): ):
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.node_type: if "ShotPrompt" in self.vertex_type:
tools = ( tools = (
[tool_node.build() for tool_node in tools] [tool_node.build() for tool_node in tools]
if tools is not None if tools is not None
@ -89,7 +84,31 @@ class PromptNode(Node):
return self._built_object return self._built_object
class LLMNode(Node): class ChainVertex(Vertex):
def __init__(self, data: Dict):
super().__init__(data, base_type="chains")
def build(
self,
force: bool = False,
tools: Optional[Union[List[Vertex], List[ToolVertex]]] = None,
) -> Any:
if not self._built or force:
# Check if the chain requires a PromptNode
for key, value in self.params.items():
if isinstance(value, PromptVertex):
# Build the PromptNode, passing the tools if available
self.params[key] = value.build(tools=tools, force=force)
self._build()
#! Cannot deepcopy SQLDatabaseChain
if self.vertex_type in ["SQLDatabaseChain"]:
return self._built_object
return self._built_object
class LLMVertex(Vertex):
built_node_type = None built_node_type = None
class_built_object = None class_built_object = None
@ -99,23 +118,28 @@ class LLMNode(Node):
def build(self, force: bool = False) -> Any: def build(self, force: bool = False) -> Any:
# LLM is different because some models might take up too much memory # LLM is different because some models might take up too much memory
# or time to load. So we only load them when we need them.ß # or time to load. So we only load them when we need them.ß
if self.node_type == self.built_node_type: if self.vertex_type == self.built_node_type:
return self.class_built_object return self.class_built_object
if not self._built or force: if not self._built or force:
self._build() self._build()
self.built_node_type = self.node_type self.built_node_type = self.vertex_type
self.class_built_object = self._built_object self.class_built_object = self._built_object
# Avoid deepcopying the LLM # Avoid deepcopying the LLM
# that are loaded from a file # that are loaded from a file
return self._built_object return self._built_object
class FileToolNode(ToolNode): class ToolkitVertex(Vertex):
def __init__(self, data: Dict):
super().__init__(data, base_type="toolkits")
class FileToolVertex(ToolVertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data) super().__init__(data)
class WrapperNode(Node): class WrapperVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="wrappers") super().__init__(data, base_type="wrappers")
@ -127,7 +151,7 @@ class WrapperNode(Node):
return self._built_object return self._built_object
class DocumentLoaderNode(Node): class DocumentLoaderVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="documentloaders") super().__init__(data, base_type="documentloaders")
@ -135,17 +159,17 @@ class DocumentLoaderNode(Node):
# This built_object is a list of documents. Maybe we should # This built_object is a list of documents. Maybe we should
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
return f"""{self.node_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
Documents: {self._built_object[:3]}...""" Documents: {self._built_object[:3]}..."""
return f"{self.node_type}()" return f"{self.vertex_type}()"
class EmbeddingNode(Node): class EmbeddingVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="embeddings") super().__init__(data, base_type="embeddings")
class VectorStoreNode(Node): class VectorStoreVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="vectorstores") super().__init__(data, base_type="vectorstores")
@ -153,12 +177,12 @@ class VectorStoreNode(Node):
return "Vector stores can take time to build. It will build on the first query." return "Vector stores can take time to build. It will build on the first query."
class MemoryNode(Node): class MemoryVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="memory") super().__init__(data, base_type="memory")
class TextSplitterNode(Node): class TextSplitterVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="textsplitters") super().__init__(data, base_type="textsplitters")
@ -166,26 +190,6 @@ class TextSplitterNode(Node):
# This built_object is a list of documents. Maybe we should # This built_object is a list of documents. Maybe we should
# show how many documents are in the list? # show how many documents are in the list?
if self._built_object: if self._built_object:
return f"""{self.node_type}({len(self._built_object)} documents)\nDocuments: {self._built_object[:3]}...""" return f"""{self.vertex_type}({len(self._built_object)} documents)
return f"{self.node_type}()" \nDocuments: {self._built_object[:3]}..."""
return f"{self.vertex_type}()"
class ChainNode(Node):
def __init__(self, data: Dict):
super().__init__(data, base_type="chains")
def build(
self,
force: bool = False,
tools: Optional[List[Union[ToolNode, ToolkitNode]]] = None,
) -> Any:
if not self._built or force:
# Check if the chain requires a PromptNode
for key, value in self.params.items():
if isinstance(value, PromptNode):
# Build the PromptNode, passing the tools if available
self.params[key] = value.build(tools=tools, force=force)
self._build()
return self._built_object

View file

@ -9,6 +9,7 @@ from langchain.base_language import BaseLanguageModel
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.tools import BaseTool from langchain.tools import BaseTool
from langflow.utils import validate
def import_module(module_path: str) -> Any: def import_module(module_path: str) -> Any:
@ -147,3 +148,10 @@ def import_utility(utility: str) -> Any:
if utility == "SQLDatabase": if utility == "SQLDatabase":
return import_class(f"langchain.sql_database.{utility}") return import_class(f"langchain.sql_database.{utility}")
return import_class(f"langchain.utilities.{utility}") return import_class(f"langchain.utilities.{utility}")
def get_function(code):
"""Get the function"""
function_name = validate.extract_function_name(code)
return validate.create_function(code, function_name)

View file

@ -12,6 +12,7 @@ from langchain.agents.load_tools import (
_LLM_TOOLS, _LLM_TOOLS,
) )
from langchain.agents.loading import load_agent_from_config from langchain.agents.loading import load_agent_from_config
from langflow.graph import Graph
from langchain.agents.tools import Tool from langchain.agents.tools import Tool
from langchain.base_language import BaseLanguageModel from langchain.base_language import BaseLanguageModel
from langchain.callbacks.base import BaseCallbackManager from langchain.callbacks.base import BaseCallbackManager
@ -20,12 +21,12 @@ from langchain.llms.loading import load_llm_from_config
from pydantic import ValidationError from pydantic import ValidationError
from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.importing.utils import import_by_type from langflow.interface.importing.utils import get_function, import_by_type
from langflow.interface.run import fix_memory_inputs from langflow.interface.run import fix_memory_inputs
from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.types import get_type_list from langflow.interface.types import get_type_list
from langflow.interface.utils import load_file_into_dict from langflow.interface.utils import load_file_into_dict
from langflow.utils import util, validate from langflow.utils import util
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
@ -99,11 +100,9 @@ def instantiate_tool(node_type, class_object, params):
if node_type == "JsonSpec": if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path")) params["dict_"] = load_file_into_dict(params.pop("path"))
return class_object(**params) return class_object(**params)
elif node_type == "PythonFunction": elif node_type == "PythonFunctionTool":
function_string = params["code"] params["func"] = get_function(params.get("code"))
if isinstance(function_string, str): return class_object(**params)
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif node_type.lower() == "tool": elif node_type.lower() == "tool":
return class_object(**params) return class_object(**params)
return class_object(**params) return class_object(**params)
@ -167,7 +166,6 @@ def instantiate_utility(node_type, class_object, params):
def load_flow_from_json(path: str, build=True): def load_flow_from_json(path: str, build=True):
"""Load flow from json file""" """Load flow from json file"""
# This is done to avoid circular imports # This is done to avoid circular imports
from langflow.graph import Graph
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
flow_graph = json.load(f) flow_graph = json.load(f)

View file

@ -6,7 +6,7 @@ from langchain.schema import AgentAction
from langflow.api.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler # type: ignore from langflow.api.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler # type: ignore
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
from langflow.graph.graph import Graph from langflow.graph import Graph
from langflow.utils.logger import logger from langflow.utils.logger import logger

View file

@ -71,7 +71,8 @@ class ToolCreator(LangChainTypeCreator):
for tool, tool_fcn in ALL_TOOLS_NAMES.items(): for tool, tool_fcn in ALL_TOOLS_NAMES.items():
tool_params = get_tool_params(tool_fcn) tool_params = get_tool_params(tool_fcn)
tool_name = tool_params.get("name", tool)
tool_name = tool_params.get("name") or tool
if tool_name in settings.tools or settings.dev: if tool_name in settings.tools or settings.dev:
if tool_name == "JsonSpec": if tool_name == "JsonSpec":

View file

@ -9,10 +9,10 @@ from langchain.agents.load_tools import (
from langchain.tools.json.tool import JsonSpec from langchain.tools.json.tool import JsonSpec
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
from langflow.interface.tools.custom import PythonFunction from langflow.interface.tools.custom import PythonFunctionTool
FILE_TOOLS = {"JsonSpec": JsonSpec} FILE_TOOLS = {"JsonSpec": JsonSpec}
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction} CUSTOM_TOOLS = {"Tool": Tool, "PythonFunctionTool": PythonFunctionTool}
OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}") for tool in tools.__all__} OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}") for tool in tools.__all__}

View file

@ -1,13 +1,14 @@
from typing import Callable, Optional from typing import Optional
from langflow.interface.importing.utils import get_function
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from langflow.utils import validate from langflow.utils import validate
from langchain.agents.tools import Tool
class Function(BaseModel): class Function(BaseModel):
code: str code: str
function: Optional[Callable] = None
imports: Optional[str] = None imports: Optional[str] = None
# Eval code and store the function # Eval code and store the function
@ -24,14 +25,17 @@ class Function(BaseModel):
return v return v
def get_function(self):
"""Get the function"""
function_name = validate.extract_function_name(self.code)
return validate.create_function(self.code, function_name) class PythonFunctionTool(Function, Tool):
class PythonFunction(Function):
"""Python function""" """Python function"""
name: str = "Custom Tool"
description: str
code: str code: str
def ___init__(self, name: str, description: str, code: str):
self.name = name
self.description = description
self.code = code
self.func = get_function(self.code)
super().__init__(name=name, description=description, func=self.func)

View file

@ -59,11 +59,33 @@ class ToolNode(FrontendNode):
return super().to_dict() return super().to_dict()
class PythonFunctionNode(FrontendNode): class PythonFunctionToolNode(FrontendNode):
name: str = "PythonFunction" name: str = "PythonFunctionTool"
template: Template = Template( template: Template = Template(
type_name="python_function", type_name="PythonFunctionTool",
fields=[ fields=[
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value="",
name="name",
advanced=False,
),
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value="",
name="description",
advanced=False,
),
TemplateField( TemplateField(
field_type="code", field_type="code",
required=True, required=True,
@ -73,11 +95,11 @@ class PythonFunctionNode(FrontendNode):
value=DEFAULT_PYTHON_FUNCTION, value=DEFAULT_PYTHON_FUNCTION,
name="code", name="code",
advanced=False, advanced=False,
) ),
], ],
) )
description: str = "Python function to be executed." description: str = "Python function to be executed."
base_classes: list[str] = ["function"] base_classes: list[str] = ["Tool"]
def to_dict(self): def to_dict(self):
return super().to_dict() return super().to_dict()

View file

@ -1,6 +1,6 @@
import { Disclosure } from "@headlessui/react"; import { Disclosure } from "@headlessui/react";
import { ChevronLeftIcon } from "@heroicons/react/24/outline"; import { ChevronLeftIcon } from "@heroicons/react/24/outline";
import { useContext } from "react"; import { useContext, useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
@ -13,6 +13,7 @@ export default function ExtraSidebar() {
extraNavigation, extraNavigation,
extraComponent, extraComponent,
} = useContext(locationContext); } = useContext(locationContext);
return ( return (
<> <>
<aside <aside
@ -21,10 +22,8 @@ export default function ExtraSidebar() {
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`} } flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
> >
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start"> <div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
<div className="flex pt-1 px-4 justify-between align-middle w-full"> <div className="flex px-4 justify-between align-middle w-full">
<span className="text-gray-900 dark:text-white py-[2px] font-medium "> <span className="text-gray-900 dark:text-white py-[2px] font-medium "></span>
{extraNavigation.title}
</span>
</div> </div>
<div className="flex flex-grow flex-col w-full"> <div className="flex flex-grow flex-col w-full">
{extraNavigation.options ? ( {extraNavigation.options ? (

View file

@ -15,3 +15,9 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }
/* The style below sets the cursor property of the element with the class .react-flow__pane to the default cursor.
The cursor: default; property value restores the browser's default cursor style for the targeted element. By applying this style, the element will no longer have a custom cursor appearance such as "grab" or any other custom cursor defined elsewhere in the application. Instead, it will revert to the default cursor style determined by the browser, typically an arrow-shaped cursor. */
.react-flow__pane {
cursor: default;
}

View file

@ -16,9 +16,11 @@ import Convert from "ansi-to-html";
export default function ChatMessage({ export default function ChatMessage({
chat, chat,
lockChat, lockChat,
lastMessage,
}: { }: {
chat: ChatMessageType; chat: ChatMessageType;
lockChat: boolean; lockChat: boolean;
lastMessage: boolean;
}) { }) {
const convert = new Convert({ newline: true }); const convert = new Convert({ newline: true });
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
@ -48,7 +50,7 @@ export default function ChatMessage({
"absolute transition-opacity duration-500 scale-150 " + "absolute transition-opacity duration-500 scale-150 " +
(lockChat ? "opacity-100" : "opacity-0") (lockChat ? "opacity-100" : "opacity-0")
} }
src={AiIcon} src={lastMessage ? AiIcon : AiIconStill}
/> />
<img <img
className={ className={

View file

@ -290,7 +290,9 @@ export default function ChatModal({
errors.concat( errors.concat(
template[t].required && template[t].required &&
template[t].show && template[t].show &&
(!template[t].value || template[t].value === "") && (template[t].value === undefined ||
template[t].value === null ||
template[t].value === "") &&
!reactFlowInstance !reactFlowInstance
.getEdges() .getEdges()
.some( .some(
@ -414,7 +416,12 @@ export default function ChatModal({
> >
{chatHistory.length > 0 ? ( {chatHistory.length > 0 ? (
chatHistory.map((c, i) => ( chatHistory.map((c, i) => (
<ChatMessage lockChat={lockChat} chat={c} key={i} /> <ChatMessage
lockChat={lockChat}
chat={c}
lastMessage={chatHistory.length - 1 == i ? true : false}
key={i}
/>
)) ))
) : ( ) : (
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle"> <div className="flex flex-col h-full text-center justify-center w-full items-center align-middle">

View file

@ -5,6 +5,7 @@ import { DisclosureComponentType } from "../../../../types/components";
export default function DisclosureComponent({ export default function DisclosureComponent({
button: { title, Icon, buttons = [] }, button: { title, Icon, buttons = [] },
children, children,
openDisc,
}: DisclosureComponentType) { }: DisclosureComponentType) {
return ( return (
<Disclosure as="div" key={title}> <Disclosure as="div" key={title}>
@ -27,14 +28,14 @@ export default function DisclosureComponent({
<div> <div>
<ChevronRightIcon <ChevronRightIcon
className={`${ className={`${
open ? "rotate-90 transform" : "" open || openDisc ? "rotate-90 transform" : ""
} h-4 w-4 text-gray-800 dark:text-white`} } h-4 w-4 text-gray-800 dark:text-white`}
/> />
</div> </div>
</div> </div>
</Disclosure.Button> </Disclosure.Button>
</div> </div>
<Disclosure.Panel as="div" className="-mt-px"> <Disclosure.Panel as="div" className="-mt-px" static={openDisc}>
{children} {children}
</Disclosure.Panel> </Disclosure.Panel>
</> </>

View file

@ -1,13 +1,21 @@
import { Bars2Icon } from "@heroicons/react/24/outline"; import { Bars2Icon } from "@heroicons/react/24/outline";
import DisclosureComponent from "../DisclosureComponent"; import DisclosureComponent from "../DisclosureComponent";
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils"; import {
import { useContext, useEffect, useState } from "react"; classNames,
nodeColors,
nodeIcons,
nodeNames,
} from "../../../../utils";
import { useContext, useEffect, useState, useRef } from "react";
import { typesContext } from "../../../../contexts/typesContext"; import { typesContext } from "../../../../contexts/typesContext";
import { APIClassType, APIObjectType } from "../../../../types/api"; import { APIClassType, APIObjectType } from "../../../../types/api";
import TooltipReact from "../../../../components/ReactTooltipComponent"; import TooltipReact from "../../../../components/ReactTooltipComponent";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
export default function ExtraSidebar() { export default function ExtraSidebar() {
const { data } = useContext(typesContext); const { data } = useContext(typesContext);
const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState("");
function onDragStart( function onDragStart(
event: React.DragEvent<any>, event: React.DragEvent<any>,
@ -24,12 +32,48 @@ export default function ExtraSidebar() {
event.dataTransfer.setData("json", JSON.stringify(data)); event.dataTransfer.setData("json", JSON.stringify(data));
} }
function handleSearchInput(e: string) {
setFilterData((_) => {
let ret = {};
Object.keys(data).forEach((d: keyof APIObjectType, i) => {
ret[d] = {};
let keys = Object.keys(data[d]).filter((nd) =>
nd.toLowerCase().includes(e.toLowerCase())
);
keys.forEach((element) => {
ret[d][element] = data[d][element];
});
});
return ret;
});
}
return ( return (
<>
<div className="relative mt-2 flex items-center mb-2 mx-2">
<input
type="text"
name="search"
id="search"
placeholder="Search nodes"
className="dark:text-white focus:outline-none block w-full rounded-md py-1.5 ps-3 pr-9 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 sm:text-sm sm:leading-6 dark:ring-0 dark:bg-[#2d3747] dark:focus:outline-none"
onChange={(e) => {
handleSearchInput(e.target.value);
setSearch(e.target.value);
}}
/>
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-3 items-center">
<MagnifyingGlassIcon className="h-5 w-5 dark:text-white"></MagnifyingGlassIcon>
</div>
</div>
<div className="mt-1 w-full"> <div className="mt-1 w-full">
{Object.keys(data) {Object.keys(dataFilter)
.sort() .sort()
.map((d: keyof APIObjectType, i) => ( .map((d: keyof APIObjectType, i) =>
Object.keys(dataFilter[d]).length > 0 ? (
<DisclosureComponent <DisclosureComponent
openDisc={search.length == 0 ? false : true}
key={i} key={i}
button={{ button={{
title: nodeNames[d] ?? nodeNames.unknown, title: nodeNames[d] ?? nodeNames.unknown,
@ -37,7 +81,7 @@ export default function ExtraSidebar() {
}} }}
> >
<div className="p-2 flex flex-col gap-2"> <div className="p-2 flex flex-col gap-2">
{Object.keys(data[d]) {Object.keys(dataFilter[d])
.sort() .sort()
.map((t: string, k) => ( .map((t: string, k) => (
<TooltipReact <TooltipReact
@ -52,7 +96,8 @@ export default function ExtraSidebar() {
draggable draggable
className={" cursor-grab border-l-8 rounded-l-md"} className={" cursor-grab border-l-8 rounded-l-md"}
style={{ style={{
borderLeftColor: nodeColors[d] ?? nodeColors.unknown, borderLeftColor:
nodeColors[d] ?? nodeColors.unknown,
}} }}
onDragStart={(event) => onDragStart={(event) =>
onDragStart(event, { onDragStart(event, {
@ -78,12 +123,13 @@ export default function ExtraSidebar() {
</div> </div>
</TooltipReact> </TooltipReact>
))} ))}
{Object.keys(data[d]).length === 0 && (
<div className="text-gray-400 text-center">Coming soon</div>
)}
</div> </div>
</DisclosureComponent> </DisclosureComponent>
))} ) : (
<div key={i}></div>
)
)}
</div> </div>
</>
); );
} }

View file

@ -56,6 +56,7 @@ export type FileComponentType = {
export type DisclosureComponentType = { export type DisclosureComponentType = {
children: ReactNode; children: ReactNode;
openDisc: boolean;
button: { button: {
title: string; title: string;
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>; Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;

View file

@ -2,6 +2,7 @@ import json
from pathlib import Path from pathlib import Path
from typing import AsyncGenerator from typing import AsyncGenerator
from langflow.graph.graph.base import Graph
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from httpx import AsyncClient from httpx import AsyncClient
@ -46,7 +47,6 @@ def client():
def get_graph(_type="basic"): def get_graph(_type="basic"):
"""Get a graph from a json file""" """Get a graph from a json file"""
from langflow.graph.graph import Graph
if _type == "basic": if _type == "basic":
path = pytest.BASIC_EXAMPLE_PATH path = pytest.BASIC_EXAMPLE_PATH

View file

@ -197,7 +197,7 @@
"y": 136.29836646158452 "y": 136.29836646158452
}, },
"data": { "data": {
"type": "PythonFunction", "type": "PythonFunctionTool",
"node": { "node": {
"template": { "template": {
"code": { "code": {
@ -210,6 +210,26 @@
"type": "str", "type": "str",
"list": false "list": false
}, },
"description": {
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"value": "My description",
"name": "description",
"type": "str",
"list": false
},
"name": {
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"value": "My Tool",
"name": "name",
"type": "str",
"list": false
},
"_type": "python_function" "_type": "python_function"
}, },
"description": "Python function to be executed.", "description": "Python function to be executed.",

View file

@ -1,16 +1,23 @@
# Test this: # Test this:
from langflow.interface.importing.utils import get_function
import pytest import pytest
from langflow.interface.tools.custom import PythonFunction from langflow.interface.tools.custom import PythonFunctionTool
from langflow.utils import constants from langflow.utils import constants
def test_python_function(): def test_python_function():
"""Test Python function""" """Test Python function"""
func = PythonFunction(code=constants.DEFAULT_PYTHON_FUNCTION) code = constants.DEFAULT_PYTHON_FUNCTION
assert func.get_function()("text") == "text" func = get_function(code)
func = PythonFunctionTool(name="Test", description="Testing", code=code, func=func)
assert func("text") == "text"
# the tool decorator should raise an error if # the tool decorator should raise an error if
# the function is not str -> str # the function is not str -> str
# This raises ValidationError # This raises ValidationError
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
func = PythonFunction(code=pytest.CODE_WITH_SYNTAX_ERROR) code = pytest.CODE_WITH_SYNTAX_ERROR
func = get_function(code)
func = PythonFunctionTool(
name="Test", description="Testing", code=code, func=func
)

View file

@ -1,18 +1,20 @@
from typing import Type, Union from typing import Type, Union
from langflow.graph.edge.base import Edge
from langflow.graph.vertex.base import Vertex
import pytest import pytest
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.llms.fake import FakeListLLM from langchain.llms.fake import FakeListLLM
from langflow.graph import Edge, Graph, Node from langflow.graph import Graph
from langflow.graph.nodes import ( from langflow.graph.vertex.types import (
AgentNode, AgentVertex,
ChainNode, ChainVertex,
FileToolNode, FileToolVertex,
LLMNode, LLMVertex,
PromptNode, PromptVertex,
ToolkitNode, ToolkitVertex,
ToolNode, ToolVertex,
WrapperNode, WrapperVertex,
) )
from langflow.interface.run import get_result_and_thought from langflow.interface.run import get_result_and_thought
from langflow.utils.payload import get_root_node from langflow.utils.payload import get_root_node
@ -23,7 +25,7 @@ from langflow.utils.payload import get_root_node
# BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH # BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH
def get_node_by_type(graph, node_type: Type[Node]) -> Union[Node, None]: def get_node_by_type(graph, node_type: Type[Vertex]) -> Union[Vertex, None]:
"""Get a node by type""" """Get a node by type"""
return next((node for node in graph.nodes if isinstance(node, node_type)), None) return next((node for node in graph.nodes if isinstance(node, node_type)), None)
@ -33,7 +35,7 @@ def test_graph_structure(basic_graph):
assert len(basic_graph.nodes) > 0 assert len(basic_graph.nodes) > 0
assert len(basic_graph.edges) > 0 assert len(basic_graph.edges) > 0
for node in basic_graph.nodes: for node in basic_graph.nodes:
assert isinstance(node, Node) assert isinstance(node, Vertex)
for edge in basic_graph.edges: for edge in basic_graph.edges:
assert isinstance(edge, Edge) assert isinstance(edge, Edge)
assert edge.source in basic_graph.nodes assert edge.source in basic_graph.nodes
@ -156,14 +158,16 @@ def test_get_node_neighbors_complex(complex_graph):
tool_neighbors = complex_graph.get_nodes_with_target(tool) tool_neighbors = complex_graph.get_nodes_with_target(tool)
assert tool_neighbors is not None assert tool_neighbors is not None
# Check if there is a PythonFunction in the tool's neighbors # Check if there is a PythonFunction in the tool's neighbors
assert any("PythonFunction" in neighbor.data["type"] for neighbor in tool_neighbors) assert any(
"PythonFunctionTool" in neighbor.data["type"] for neighbor in tool_neighbors
)
def test_get_node(basic_graph): def test_get_node(basic_graph):
"""Test getting a single node""" """Test getting a single node"""
node_id = basic_graph.nodes[0].id node_id = basic_graph.nodes[0].id
node = basic_graph.get_node(node_id) node = basic_graph.get_node(node_id)
assert isinstance(node, Node) assert isinstance(node, Vertex)
assert node.id == node_id assert node.id == node_id
@ -172,7 +176,7 @@ def test_build_nodes(basic_graph):
assert len(basic_graph.nodes) == len(basic_graph._nodes) assert len(basic_graph.nodes) == len(basic_graph._nodes)
for node in basic_graph.nodes: for node in basic_graph.nodes:
assert isinstance(node, Node) assert isinstance(node, Vertex)
def test_build_edges(basic_graph): def test_build_edges(basic_graph):
@ -180,8 +184,8 @@ def test_build_edges(basic_graph):
assert len(basic_graph.edges) == len(basic_graph._edges) assert len(basic_graph.edges) == len(basic_graph._edges)
for edge in basic_graph.edges: for edge in basic_graph.edges:
assert isinstance(edge, Edge) assert isinstance(edge, Edge)
assert isinstance(edge.source, Node) assert isinstance(edge.source, Vertex)
assert isinstance(edge.target, Node) assert isinstance(edge.target, Vertex)
def test_get_root_node(basic_graph, complex_graph): def test_get_root_node(basic_graph, complex_graph):
@ -189,13 +193,13 @@ def test_get_root_node(basic_graph, complex_graph):
assert isinstance(basic_graph, Graph) assert isinstance(basic_graph, Graph)
root = get_root_node(basic_graph) root = get_root_node(basic_graph)
assert root is not None assert root is not None
assert isinstance(root, Node) assert isinstance(root, Vertex)
assert root.data["type"] == "TimeTravelGuideChain" assert root.data["type"] == "TimeTravelGuideChain"
# For complex example, the root node is a ZeroShotAgent too # For complex example, the root node is a ZeroShotAgent too
assert isinstance(complex_graph, Graph) assert isinstance(complex_graph, Graph)
root = get_root_node(complex_graph) root = get_root_node(complex_graph)
assert root is not None assert root is not None
assert isinstance(root, Node) assert isinstance(root, Vertex)
assert root.data["type"] == "ZeroShotAgent" assert root.data["type"] == "ZeroShotAgent"
@ -254,14 +258,14 @@ def assert_agent_was_built(graph):
def test_agent_node_build(complex_graph): def test_agent_node_build(complex_graph):
agent_node = get_node_by_type(complex_graph, AgentNode) agent_node = get_node_by_type(complex_graph, AgentVertex)
assert agent_node is not None assert agent_node is not None
built_object = agent_node.build() built_object = agent_node.build()
assert built_object is not None assert built_object is not None
def test_tool_node_build(complex_graph): def test_tool_node_build(complex_graph):
tool_node = get_node_by_type(complex_graph, ToolNode) tool_node = get_node_by_type(complex_graph, ToolVertex)
assert tool_node is not None assert tool_node is not None
built_object = tool_node.build() built_object = tool_node.build()
assert built_object is not None assert built_object is not None
@ -269,7 +273,7 @@ def test_tool_node_build(complex_graph):
def test_chain_node_build(complex_graph): def test_chain_node_build(complex_graph):
chain_node = get_node_by_type(complex_graph, ChainNode) chain_node = get_node_by_type(complex_graph, ChainVertex)
assert chain_node is not None assert chain_node is not None
built_object = chain_node.build() built_object = chain_node.build()
assert built_object is not None assert built_object is not None
@ -277,7 +281,7 @@ def test_chain_node_build(complex_graph):
def test_prompt_node_build(complex_graph): def test_prompt_node_build(complex_graph):
prompt_node = get_node_by_type(complex_graph, PromptNode) prompt_node = get_node_by_type(complex_graph, PromptVertex)
assert prompt_node is not None assert prompt_node is not None
built_object = prompt_node.build() built_object = prompt_node.build()
assert built_object is not None assert built_object is not None
@ -285,7 +289,7 @@ def test_prompt_node_build(complex_graph):
def test_llm_node_build(basic_graph): def test_llm_node_build(basic_graph):
llm_node = get_node_by_type(basic_graph, LLMNode) llm_node = get_node_by_type(basic_graph, LLMVertex)
assert llm_node is not None assert llm_node is not None
built_object = llm_node.build() built_object = llm_node.build()
assert built_object is not None assert built_object is not None
@ -293,7 +297,7 @@ def test_llm_node_build(basic_graph):
def test_toolkit_node_build(openapi_graph): def test_toolkit_node_build(openapi_graph):
toolkit_node = get_node_by_type(openapi_graph, ToolkitNode) toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
assert toolkit_node is not None assert toolkit_node is not None
built_object = toolkit_node.build() built_object = toolkit_node.build()
assert built_object is not None assert built_object is not None
@ -301,7 +305,7 @@ def test_toolkit_node_build(openapi_graph):
def test_file_tool_node_build(openapi_graph): def test_file_tool_node_build(openapi_graph):
file_tool_node = get_node_by_type(openapi_graph, FileToolNode) file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
assert file_tool_node is not None assert file_tool_node is not None
built_object = file_tool_node.build() built_object = file_tool_node.build()
assert built_object is not None assert built_object is not None
@ -309,7 +313,7 @@ def test_file_tool_node_build(openapi_graph):
def test_wrapper_node_build(openapi_graph): def test_wrapper_node_build(openapi_graph):
wrapper_node = get_node_by_type(openapi_graph, WrapperNode) wrapper_node = get_node_by_type(openapi_graph, WrapperVertex)
assert wrapper_node is not None assert wrapper_node is not None
built_object = wrapper_node.build() built_object = wrapper_node.build()
assert built_object is not None assert built_object is not None
@ -324,7 +328,7 @@ def test_get_result_and_thought(basic_graph):
message = "Hello" message = "Hello"
# Find the node that is an LLMNode and change the # Find the node that is an LLMNode and change the
# _built_object to a FakeListLLM # _built_object to a FakeListLLM
llm_node = get_node_by_type(basic_graph, LLMNode) llm_node = get_node_by_type(basic_graph, LLMVertex)
assert llm_node is not None assert llm_node is not None
llm_node._built_object = FakeListLLM(responses=responses) llm_node._built_object = FakeListLLM(responses=responses)
llm_node._built = True llm_node._built = True