Merge branch 'feature/store' of github.com:logspace-ai/langflow into feature/store
This commit is contained in:
commit
9e653f2bdc
39 changed files with 641 additions and 481 deletions
108
poetry.lock
generated
108
poetry.lock
generated
|
|
@ -411,17 +411,17 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "boto3"
|
||||
version = "1.29.5"
|
||||
version = "1.29.6"
|
||||
description = "The AWS SDK for Python"
|
||||
optional = false
|
||||
python-versions = ">= 3.7"
|
||||
files = [
|
||||
{file = "boto3-1.29.5-py3-none-any.whl", hash = "sha256:030b0f0faf8d44f97e67a5411644243482f33ebf1c45338bb40662239a16dda4"},
|
||||
{file = "boto3-1.29.5.tar.gz", hash = "sha256:76fc6a17781c27558c526e899579ccf530df10eb279261fe7800540f0043917e"},
|
||||
{file = "boto3-1.29.6-py3-none-any.whl", hash = "sha256:f4d19e01d176c3a5a05e4af733185ff1891b08a3c38d4a439800fa132aa6e9be"},
|
||||
{file = "boto3-1.29.6.tar.gz", hash = "sha256:d1d0d979a70bf9b0b13ae3b017f8523708ad953f62d16f39a602d67ee9b25554"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
botocore = ">=1.32.5,<1.33.0"
|
||||
botocore = ">=1.32.6,<1.33.0"
|
||||
jmespath = ">=0.7.1,<2.0.0"
|
||||
s3transfer = ">=0.7.0,<0.8.0"
|
||||
|
||||
|
|
@ -430,13 +430,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
|
|||
|
||||
[[package]]
|
||||
name = "botocore"
|
||||
version = "1.32.5"
|
||||
version = "1.32.6"
|
||||
description = "Low-level, data-driven core of boto 3."
|
||||
optional = false
|
||||
python-versions = ">= 3.7"
|
||||
files = [
|
||||
{file = "botocore-1.32.5-py3-none-any.whl", hash = "sha256:b8960c955ba275915bf022c54c896c2dac1038289d8a5ace92d1431257c0a439"},
|
||||
{file = "botocore-1.32.5.tar.gz", hash = "sha256:75a68f942cd87baff83b3a20dfda11b3aeda48aad32e4dcd6fe8992c0cb0e7db"},
|
||||
{file = "botocore-1.32.6-py3-none-any.whl", hash = "sha256:4454f967a4d1a01e3e6205c070455bc4e8fd53b5b0753221581ae679c55a9dfd"},
|
||||
{file = "botocore-1.32.6.tar.gz", hash = "sha256:ecec876103783b5efe6099762dda60c2af67e45f7c0ab4568e8265d11c6c449b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -3488,13 +3488,13 @@ zookeeper = ["kazoo (>=2.8.0)"]
|
|||
|
||||
[[package]]
|
||||
name = "langchain"
|
||||
version = "0.0.339"
|
||||
version = "0.0.340"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = ">=3.8.1,<4.0"
|
||||
files = [
|
||||
{file = "langchain-0.0.339-py3-none-any.whl", hash = "sha256:fec250074a6fbb3711a51423d830006d69f34aedb67604df39c642be80852cbb"},
|
||||
{file = "langchain-0.0.339.tar.gz", hash = "sha256:34eb4d7987d979663e361da435479c6f0648a170dae3eb1e9f0f7417f033a2c1"},
|
||||
{file = "langchain-0.0.340-py3-none-any.whl", hash = "sha256:f80f40b52ef82424e38e894db8b8048b6505da100679e72613316f8d8b0243fb"},
|
||||
{file = "langchain-0.0.340.tar.gz", hash = "sha256:1a6bd2511bbb81e42d2a3d7291ee03de180accab851181ee9fdbb7fbaef6c57c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -3559,13 +3559,13 @@ six = "*"
|
|||
|
||||
[[package]]
|
||||
name = "langfuse"
|
||||
version = "1.7.4"
|
||||
version = "1.7.5"
|
||||
description = "A client library for accessing langfuse"
|
||||
optional = false
|
||||
python-versions = ">=3.8.1,<4.0"
|
||||
files = [
|
||||
{file = "langfuse-1.7.4-py3-none-any.whl", hash = "sha256:f5f1e19eac2d01e9854f567f0946f47dac3be59ee40f335e616355b3545018f3"},
|
||||
{file = "langfuse-1.7.4.tar.gz", hash = "sha256:5813d2f43e7ba106ae58f048d81c7091fd681be73b35d87d53ac321f999738ae"},
|
||||
{file = "langfuse-1.7.5-py3-none-any.whl", hash = "sha256:ebbcc52f454a9c7cfc9f382e66fddafddb0219f9233598317bbcb66c215b39b6"},
|
||||
{file = "langfuse-1.7.5.tar.gz", hash = "sha256:99fc5a30b157a16cc3dcb82e84af13fabc2fd0d192be32ef2ad6d9a7fe27d130"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -3936,13 +3936,13 @@ typing-extensions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "metaphor-python"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
description = "A Python package for the Metaphor API."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "metaphor-python-0.1.20.tar.gz", hash = "sha256:a1ee7a3b21ff8644553a73bc08a4d475abed182d9a1a0a72c729910837081d50"},
|
||||
{file = "metaphor_python-0.1.20-py3-none-any.whl", hash = "sha256:ac06b5bf86f6fb1b2371b8be6589766da8eccd6876d56e94b738654dea9adc9a"},
|
||||
{file = "metaphor-python-0.1.21.tar.gz", hash = "sha256:72604c45c7bf447613f9cdf713c6c57612f1790ead2e78b13c65588e5d7aa279"},
|
||||
{file = "metaphor_python-0.1.21-py3-none-any.whl", hash = "sha256:b17099f6c37e26a77fbb77a242163fa6b64aad487687264f4ec6c9d16665c5a8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -4169,38 +4169,38 @@ dill = ">=0.3.7"
|
|||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.7.0"
|
||||
version = "1.7.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"},
|
||||
{file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"},
|
||||
{file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"},
|
||||
{file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"},
|
||||
{file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"},
|
||||
{file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"},
|
||||
{file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"},
|
||||
{file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"},
|
||||
{file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"},
|
||||
{file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"},
|
||||
{file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"},
|
||||
{file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"},
|
||||
{file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"},
|
||||
{file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"},
|
||||
{file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"},
|
||||
{file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"},
|
||||
{file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"},
|
||||
{file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"},
|
||||
{file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"},
|
||||
{file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"},
|
||||
{file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"},
|
||||
{file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"},
|
||||
{file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"},
|
||||
{file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"},
|
||||
{file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"},
|
||||
{file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"},
|
||||
{file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"},
|
||||
{file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"},
|
||||
{file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"},
|
||||
{file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"},
|
||||
{file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"},
|
||||
{file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
|
||||
{file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
|
||||
{file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
|
||||
{file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"},
|
||||
{file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"},
|
||||
{file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"},
|
||||
{file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"},
|
||||
{file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"},
|
||||
{file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"},
|
||||
{file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"},
|
||||
{file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"},
|
||||
{file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"},
|
||||
{file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"},
|
||||
{file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
|
||||
{file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -6143,6 +6143,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
|||
[package.extras]
|
||||
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-asyncio"
|
||||
version = "0.21.1"
|
||||
description = "Pytest support for asyncio"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pytest-asyncio-0.21.1.tar.gz", hash = "sha256:40a7eae6dded22c7b604986855ea48400ab15b069ae38116e8c01238e9eeb64d"},
|
||||
{file = "pytest_asyncio-0.21.1-py3-none-any.whl", hash = "sha256:8666c1c8ac02631d7c51ba282e0c69a8a452b211ffedf2599099845da5c5c37b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
|
||||
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "4.1.0"
|
||||
|
|
@ -9078,4 +9096,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.11"
|
||||
content-hash = "8335fd767d4ff476b3caf2a9e05fcce1f353d4b3a2e181585080a618099669fa"
|
||||
content-hash = "62e47482eefda134f0801744360624549d7024861380f51f280f60450d768615"
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ qianfan = "0.0.5"
|
|||
pgvector = "^0.2.3"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest-asyncio = "^0.21.1"
|
||||
types-redis = "^4.6.0.5"
|
||||
ipykernel = "^6.21.2"
|
||||
mypy = "^1.1.1"
|
||||
|
|
|
|||
|
|
@ -8,21 +8,20 @@ from fastapi import (
|
|||
status,
|
||||
)
|
||||
from fastapi.responses import StreamingResponse
|
||||
from loguru import logger
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.api.utils import build_input_keys_response
|
||||
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
||||
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.services.auth.utils import (
|
||||
get_current_active_user,
|
||||
get_current_user_by_jwt,
|
||||
)
|
||||
from langflow.services.cache.utils import update_build_status
|
||||
from loguru import logger
|
||||
from langflow.services.deps import get_chat_service, get_session, get_cache_service
|
||||
from sqlmodel import Session
|
||||
from langflow.services.chat.service import ChatService
|
||||
from langflow.services.cache.service import BaseCacheService
|
||||
|
||||
from langflow.services.cache.utils import update_build_status
|
||||
from langflow.services.chat.service import ChatService
|
||||
from langflow.services.deps import get_cache_service, get_chat_service, get_session
|
||||
|
||||
router = APIRouter(tags=["Chat"])
|
||||
|
||||
|
|
@ -164,9 +163,9 @@ async def stream_build(
|
|||
}
|
||||
yield str(StreamData(event="log", data=log_dict))
|
||||
if vertex.is_task:
|
||||
vertex = try_running_celery_task(vertex, user_id)
|
||||
vertex = await try_running_celery_task(vertex, user_id)
|
||||
else:
|
||||
vertex.build(user_id=user_id)
|
||||
await vertex.build(user_id=user_id)
|
||||
params = vertex._built_object_repr()
|
||||
valid = True
|
||||
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
||||
|
|
@ -193,7 +192,7 @@ async def stream_build(
|
|||
|
||||
yield str(StreamData(event="message", data=response))
|
||||
|
||||
langchain_object = graph.build()
|
||||
langchain_object = await graph.build()
|
||||
# Now we need to check the input_keys to send them to the client
|
||||
if hasattr(langchain_object, "input_keys"):
|
||||
input_keys_response = build_input_keys_response(langchain_object, artifacts)
|
||||
|
|
@ -224,7 +223,7 @@ async def stream_build(
|
|||
raise HTTPException(status_code=500, detail=str(exc))
|
||||
|
||||
|
||||
def try_running_celery_task(vertex, user_id):
|
||||
async def try_running_celery_task(vertex, user_id):
|
||||
# Try running the task in celery
|
||||
# and set the task_id to the local vertex
|
||||
# if it fails, run the task locally
|
||||
|
|
@ -236,5 +235,5 @@ def try_running_celery_task(vertex, user_id):
|
|||
except Exception as exc:
|
||||
logger.debug(f"Error running task in celery: {exc}")
|
||||
vertex.task_id = None
|
||||
vertex.build(user_id=user_id)
|
||||
await vertex.build(user_id=user_id)
|
||||
return vertex
|
||||
|
|
|
|||
|
|
@ -1,33 +1,30 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Annotated, Optional, Union
|
||||
from langflow.services.auth.utils import api_key_security, get_current_active_user
|
||||
|
||||
|
||||
from langflow.services.cache.utils import save_uploaded_file
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.deps import (
|
||||
get_session_service,
|
||||
get_settings_service,
|
||||
get_task_service,
|
||||
)
|
||||
from loguru import logger
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body, status
|
||||
import sqlalchemy as sa
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
|
||||
from loguru import logger
|
||||
|
||||
from langflow.api.v1.schemas import (
|
||||
CustomComponentCode,
|
||||
ProcessResponse,
|
||||
TaskResponse,
|
||||
TaskStatusResponse,
|
||||
UploadFileResponse,
|
||||
CustomComponentCode,
|
||||
)
|
||||
|
||||
|
||||
from langflow.services.deps import get_session
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||
from langflow.services.auth.utils import api_key_security, get_current_active_user
|
||||
from langflow.services.cache.utils import save_uploaded_file
|
||||
from langflow.services.database.models.flow import Flow
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.deps import (
|
||||
get_session,
|
||||
get_session_service,
|
||||
get_settings_service,
|
||||
get_task_service,
|
||||
)
|
||||
|
||||
try:
|
||||
from langflow.worker import process_graph_cached_task
|
||||
|
|
@ -39,7 +36,6 @@ except ImportError:
|
|||
|
||||
from sqlmodel import Session
|
||||
|
||||
|
||||
from langflow.services.task.service import TaskService
|
||||
|
||||
# build router
|
||||
|
|
@ -217,6 +213,25 @@ async def custom_component(
|
|||
)
|
||||
|
||||
extractor = CustomComponent(code=raw_code.code)
|
||||
extractor.is_check_valid()
|
||||
extractor.validate()
|
||||
|
||||
return build_langchain_template_custom_component(extractor, user_id=user.id)
|
||||
|
||||
|
||||
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
||||
async def reload_custom_component(path: str):
|
||||
from langflow.interface.types import (
|
||||
build_langchain_template_custom_component,
|
||||
)
|
||||
|
||||
try:
|
||||
reader = DirectoryReader("")
|
||||
valid, content = reader.process_file(path)
|
||||
if not valid:
|
||||
raise ValueError(content)
|
||||
|
||||
extractor = CustomComponent(code=content)
|
||||
extractor.validate()
|
||||
return build_langchain_template_custom_component(extractor, user_id=user.id)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
|
|
|
|||
37
src/backend/langflow/components/agents/AgentInitializer.py
Normal file
37
src/backend/langflow/components/agents/AgentInitializer.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
from typing import Callable, List, Union
|
||||
|
||||
from langchain.agents import AgentExecutor, AgentType, initialize_agent, types
|
||||
|
||||
from langflow import CustomComponent
|
||||
from langflow.field_typing import BaseChatMemory, BaseLanguageModel, Tool
|
||||
|
||||
|
||||
class AgentInitializerComponent(CustomComponent):
|
||||
display_name: str = "Agent Initializer"
|
||||
description: str = "Initialize a Langchain Agent."
|
||||
documentation: str = "https://python.langchain.com/docs/modules/agents/agent_types/"
|
||||
|
||||
def build_config(self):
|
||||
agents = list(types.AGENT_TO_CLASS.keys())
|
||||
# field_type and required are optional
|
||||
return {
|
||||
"agent": {"options": agents, "value": agents[0], "display_name": "Agent Type"},
|
||||
"max_iterations": {"display_name": "Max Iterations", "value": 10},
|
||||
"memory": {"display_name": "Memory"},
|
||||
"tools": {"display_name": "Tools"},
|
||||
"llm": {"display_name": "Language Model"},
|
||||
}
|
||||
|
||||
def build(
|
||||
self, agent: str, llm: BaseLanguageModel, memory: BaseChatMemory, tools: List[Tool], max_iterations: int
|
||||
) -> Union[AgentExecutor, Callable]:
|
||||
agent = AgentType(agent)
|
||||
return initialize_agent(
|
||||
tools=tools,
|
||||
llm=llm,
|
||||
agent=agent,
|
||||
memory=memory,
|
||||
return_intermediate_steps=True,
|
||||
handle_parsing_errors=True,
|
||||
max_iterations=max_iterations,
|
||||
)
|
||||
|
|
@ -1,17 +1,15 @@
|
|||
from langflow import CustomComponent
|
||||
from typing import Optional
|
||||
from langchain.prompts import SystemMessagePromptTemplate
|
||||
from langchain.tools import Tool
|
||||
from langchain.schema.memory import BaseMemory
|
||||
from langchain.chat_models import ChatOpenAI
|
||||
from typing import List, Optional
|
||||
|
||||
from langchain.agents.agent import AgentExecutor
|
||||
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import _get_default_system_message
|
||||
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
||||
from langchain.chat_models import ChatOpenAI
|
||||
from langchain.memory.token_buffer import ConversationTokenBufferMemory
|
||||
from langchain.prompts import SystemMessagePromptTemplate
|
||||
from langchain.prompts.chat import MessagesPlaceholder
|
||||
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import (
|
||||
_get_default_system_message,
|
||||
)
|
||||
from langchain.schema.memory import BaseMemory
|
||||
from langchain.tools import Tool
|
||||
from langflow import CustomComponent
|
||||
|
||||
|
||||
class ConversationalAgent(CustomComponent):
|
||||
|
|
@ -27,7 +25,7 @@ class ConversationalAgent(CustomComponent):
|
|||
"gpt-4-32k",
|
||||
]
|
||||
return {
|
||||
"tools": {"is_list": True, "display_name": "Tools"},
|
||||
"tools": {"display_name": "Tools"},
|
||||
"memory": {"display_name": "Memory"},
|
||||
"system_message": {"display_name": "System Message"},
|
||||
"max_token_limit": {"display_name": "Max Token Limit"},
|
||||
|
|
@ -43,7 +41,7 @@ class ConversationalAgent(CustomComponent):
|
|||
self,
|
||||
model_name: str,
|
||||
openai_api_key: str,
|
||||
tools: Tool,
|
||||
tools: List[Tool],
|
||||
openai_api_base: Optional[str] = None,
|
||||
memory: Optional[BaseMemory] = None,
|
||||
system_message: Optional[SystemMessagePromptTemplate] = None,
|
||||
|
|
@ -51,8 +49,8 @@ class ConversationalAgent(CustomComponent):
|
|||
) -> AgentExecutor:
|
||||
llm = ChatOpenAI(
|
||||
model=model_name,
|
||||
openai_api_key=openai_api_key,
|
||||
openai_api_base=openai_api_base,
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_api_base,
|
||||
)
|
||||
if not memory:
|
||||
memory_key = "chat_history"
|
||||
|
|
|
|||
|
|
@ -1,119 +1,7 @@
|
|||
from langflow import CustomComponent
|
||||
from langchain.schema import Document
|
||||
from typing import Any, Dict, List
|
||||
|
||||
loaders_info: List[Dict[str, Any]] = [
|
||||
{
|
||||
"loader": "AirbyteJSONLoader",
|
||||
"name": "Airbyte JSON (.jsonl)",
|
||||
"import": "langchain.document_loaders.AirbyteJSONLoader",
|
||||
"defaultFor": ["jsonl"],
|
||||
"allowdTypes": ["jsonl"],
|
||||
},
|
||||
{
|
||||
"loader": "JSONLoader",
|
||||
"name": "JSON (.json)",
|
||||
"import": "langchain.document_loaders.JSONLoader",
|
||||
"defaultFor": ["json"],
|
||||
"allowdTypes": ["json"],
|
||||
},
|
||||
{
|
||||
"loader": "BSHTMLLoader",
|
||||
"name": "BeautifulSoup4 HTML (.html, .htm)",
|
||||
"import": "langchain.document_loaders.BSHTMLLoader",
|
||||
"allowdTypes": ["html", "htm"],
|
||||
},
|
||||
{
|
||||
"loader": "CSVLoader",
|
||||
"name": "CSV (.csv)",
|
||||
"import": "langchain.document_loaders.CSVLoader",
|
||||
"defaultFor": ["csv"],
|
||||
"allowdTypes": ["csv"],
|
||||
},
|
||||
{
|
||||
"loader": "CoNLLULoader",
|
||||
"name": "CoNLL-U (.conllu)",
|
||||
"import": "langchain.document_loaders.CoNLLULoader",
|
||||
"defaultFor": ["conllu"],
|
||||
"allowdTypes": ["conllu"],
|
||||
},
|
||||
{
|
||||
"loader": "EverNoteLoader",
|
||||
"name": "EverNote (.enex)",
|
||||
"import": "langchain.document_loaders.EverNoteLoader",
|
||||
"defaultFor": ["enex"],
|
||||
"allowdTypes": ["enex"],
|
||||
},
|
||||
{
|
||||
"loader": "FacebookChatLoader",
|
||||
"name": "Facebook Chat (.json)",
|
||||
"import": "langchain.document_loaders.FacebookChatLoader",
|
||||
"allowdTypes": ["json"],
|
||||
},
|
||||
{
|
||||
"loader": "OutlookMessageLoader",
|
||||
"name": "Outlook Message (.msg)",
|
||||
"import": "langchain.document_loaders.OutlookMessageLoader",
|
||||
"defaultFor": ["msg"],
|
||||
"allowdTypes": ["msg"],
|
||||
},
|
||||
{
|
||||
"loader": "PyPDFLoader",
|
||||
"name": "PyPDF (.pdf)",
|
||||
"import": "langchain.document_loaders.PyPDFLoader",
|
||||
"defaultFor": ["pdf"],
|
||||
"allowdTypes": ["pdf"],
|
||||
},
|
||||
{
|
||||
"loader": "STRLoader",
|
||||
"name": "Subtitle (.str)",
|
||||
"import": "langchain.document_loaders.STRLoader",
|
||||
"defaultFor": ["str"],
|
||||
"allowdTypes": ["str"],
|
||||
},
|
||||
{
|
||||
"loader": "TextLoader",
|
||||
"name": "Text (.txt)",
|
||||
"import": "langchain.document_loaders.TextLoader",
|
||||
"defaultFor": ["txt"],
|
||||
"allowdTypes": ["txt"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredEmailLoader",
|
||||
"name": "Unstructured Email (.eml)",
|
||||
"import": "langchain.document_loaders.UnstructuredEmailLoader",
|
||||
"defaultFor": ["eml"],
|
||||
"allowdTypes": ["eml"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredHTMLLoader",
|
||||
"name": "Unstructured HTML (.html, .htm)",
|
||||
"import": "langchain.document_loaders.UnstructuredHTMLLoader",
|
||||
"defaultFor": ["html", "htm"],
|
||||
"allowdTypes": ["html", "htm"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredMarkdownLoader",
|
||||
"name": "Unstructured Markdown (.md)",
|
||||
"import": "langchain.document_loaders.UnstructuredMarkdownLoader",
|
||||
"defaultFor": ["md"],
|
||||
"allowdTypes": ["md"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredPowerPointLoader",
|
||||
"name": "Unstructured PowerPoint (.pptx)",
|
||||
"import": "langchain.document_loaders.UnstructuredPowerPointLoader",
|
||||
"defaultFor": ["pptx"],
|
||||
"allowdTypes": ["pptx"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredWordLoader",
|
||||
"name": "Unstructured Word (.docx)",
|
||||
"import": "langchain.document_loaders.UnstructuredWordLoader",
|
||||
"defaultFor": ["docx"],
|
||||
"allowdTypes": ["docx"],
|
||||
},
|
||||
]
|
||||
from langflow import CustomComponent
|
||||
from langflow.utils.constants import LOADERS_INFO
|
||||
|
||||
|
||||
class FileLoaderComponent(CustomComponent):
|
||||
|
|
@ -122,12 +10,12 @@ class FileLoaderComponent(CustomComponent):
|
|||
beta = True
|
||||
|
||||
def build_config(self):
|
||||
loader_options = ["Automatic"] + [loader_info["name"] for loader_info in loaders_info]
|
||||
loader_options = ["Automatic"] + [loader_info["name"] for loader_info in LOADERS_INFO]
|
||||
|
||||
file_types = []
|
||||
suffixes = []
|
||||
|
||||
for loader_info in loaders_info:
|
||||
for loader_info in LOADERS_INFO:
|
||||
if "allowedTypes" in loader_info:
|
||||
file_types.extend(loader_info["allowedTypes"])
|
||||
suffixes.extend([f".{ext}" for ext in loader_info["allowedTypes"]])
|
||||
|
|
@ -189,7 +77,7 @@ class FileLoaderComponent(CustomComponent):
|
|||
|
||||
# Mapeie o nome do loader selecionado para suas informações
|
||||
selected_loader_info = None
|
||||
for loader_info in loaders_info:
|
||||
for loader_info in LOADERS_INFO:
|
||||
if loader_info["name"] == loader:
|
||||
selected_loader_info = loader_info
|
||||
break
|
||||
|
|
@ -200,7 +88,7 @@ class FileLoaderComponent(CustomComponent):
|
|||
if loader == "Automatic":
|
||||
# Determine o loader automaticamente com base na extensão do arquivo
|
||||
default_loader_info = None
|
||||
for info in loaders_info:
|
||||
for info in LOADERS_INFO:
|
||||
if "defaultFor" in info and file_type in info["defaultFor"]:
|
||||
default_loader_info = info
|
||||
break
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ agents:
|
|||
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/openapi"
|
||||
CSVAgent:
|
||||
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/csv"
|
||||
AgentInitializer:
|
||||
documentation: "https://python.langchain.com/docs/modules/agents/agent_types/"
|
||||
VectorStoreAgent:
|
||||
documentation: ""
|
||||
VectorStoreRouterAgent:
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ from typing import Callable, Dict, Union
|
|||
from langchain.agents.agent import AgentExecutor
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.document_loaders.base import BaseLoader
|
||||
from langchain.llms.base import BaseLanguageModel, BaseLLM
|
||||
from langchain.llms.base import BaseLLM
|
||||
from langchain.memory.chat_memory import BaseChatMemory
|
||||
from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||
from langchain.schema import BaseOutputParser, BaseRetriever, Document
|
||||
from langchain.schema.embeddings import Embeddings
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
from langchain.schema.memory import BaseMemory
|
||||
from langchain.text_splitter import TextSplitter
|
||||
from langchain.tools import Tool
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from typing import Dict, Generator, List, Type, Union
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from loguru import logger
|
||||
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
||||
from langflow.graph.graph.utils import process_flow
|
||||
|
|
@ -8,7 +10,6 @@ from langflow.graph.vertex.base import Vertex
|
|||
from langflow.graph.vertex.types import FileToolVertex, LLMVertex, ToolkitVertex
|
||||
from langflow.interface.tools.constants import FILE_TOOLS
|
||||
from langflow.utils import payload
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class Graph:
|
||||
|
|
@ -116,13 +117,13 @@ class Graph:
|
|||
connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node]
|
||||
return connected_nodes
|
||||
|
||||
def build(self) -> Chain:
|
||||
async def build(self) -> Chain:
|
||||
"""Builds the graph."""
|
||||
# Get root node
|
||||
root_node = payload.get_root_node(self)
|
||||
if root_node is None:
|
||||
raise ValueError("No root node found")
|
||||
return root_node.build()
|
||||
return await root_node.build()
|
||||
|
||||
def topological_sort(self) -> List[Vertex]:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
import ast
|
||||
import inspect
|
||||
import pickle
|
||||
import types
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from langflow.graph.utils import UnbuiltObject
|
||||
from langflow.graph.vertex.utils import is_basic_type
|
||||
from langflow.interface.initialize import loading
|
||||
from langflow.interface.listing import lazy_load_dict
|
||||
from langflow.utils.constants import DIRECT_TYPES
|
||||
from loguru import logger
|
||||
from langflow.utils.util import sync_to_async
|
||||
|
||||
|
||||
import inspect
|
||||
import types
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.edge.base import Edge
|
||||
|
||||
|
|
@ -216,18 +214,18 @@ class Vertex:
|
|||
self._raw_params = params
|
||||
self.params = params
|
||||
|
||||
def _build(self, user_id=None):
|
||||
async def _build(self, user_id=None):
|
||||
"""
|
||||
Initiate the build process.
|
||||
"""
|
||||
logger.debug(f"Building {self.vertex_type}")
|
||||
self._build_each_node_in_params_dict(user_id)
|
||||
self._get_and_instantiate_class(user_id)
|
||||
await self._build_each_node_in_params_dict(user_id)
|
||||
await self._get_and_instantiate_class(user_id)
|
||||
self._validate_built_object()
|
||||
|
||||
self._built = True
|
||||
|
||||
def _build_each_node_in_params_dict(self, user_id=None):
|
||||
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||
"""
|
||||
Iterates over each node in the params dictionary and builds it.
|
||||
"""
|
||||
|
|
@ -236,9 +234,9 @@ class Vertex:
|
|||
if value == self:
|
||||
del self.params[key]
|
||||
continue
|
||||
self._build_node_and_update_params(key, value, user_id)
|
||||
await self._build_node_and_update_params(key, value, user_id)
|
||||
elif isinstance(value, list) and self._is_list_of_nodes(value):
|
||||
self._build_list_of_nodes_and_update_params(key, value, user_id)
|
||||
await self._build_list_of_nodes_and_update_params(key, value, user_id)
|
||||
|
||||
def _is_node(self, value):
|
||||
"""
|
||||
|
|
@ -252,7 +250,7 @@ class Vertex:
|
|||
"""
|
||||
return all(self._is_node(node) for node in value)
|
||||
|
||||
def get_result(self, user_id=None, timeout=None) -> Any:
|
||||
async def get_result(self, user_id=None, timeout=None) -> Any:
|
||||
# Check if the Vertex was built already
|
||||
if self._built:
|
||||
return self._built_object
|
||||
|
|
@ -268,27 +266,27 @@ class Vertex:
|
|||
pass
|
||||
|
||||
# If there's no task_id, build the vertex locally
|
||||
self.build(user_id)
|
||||
await self.build(user_id)
|
||||
return self._built_object
|
||||
|
||||
def _build_node_and_update_params(self, key, node, user_id=None):
|
||||
async def _build_node_and_update_params(self, key, node, user_id=None):
|
||||
"""
|
||||
Builds a given node and updates the params dictionary accordingly.
|
||||
"""
|
||||
|
||||
result = node.get_result(user_id)
|
||||
result = await node.get_result(user_id)
|
||||
self._handle_func(key, result)
|
||||
if isinstance(result, list):
|
||||
self._extend_params_list_with_result(key, result)
|
||||
self.params[key] = result
|
||||
|
||||
def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||
"""
|
||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||
"""
|
||||
self.params[key] = []
|
||||
for node in nodes:
|
||||
built = node.get_result(user_id)
|
||||
built = await node.get_result(user_id)
|
||||
if isinstance(built, list):
|
||||
if key not in self.params:
|
||||
self.params[key] = []
|
||||
|
|
@ -318,14 +316,14 @@ class Vertex:
|
|||
if isinstance(self.params[key], list):
|
||||
self.params[key].extend(result)
|
||||
|
||||
def _get_and_instantiate_class(self, user_id=None):
|
||||
async def _get_and_instantiate_class(self, user_id=None):
|
||||
"""
|
||||
Gets the class from a dictionary and instantiates it with the params.
|
||||
"""
|
||||
if self.base_type is None:
|
||||
raise ValueError(f"Base type for node {self.vertex_type} not found")
|
||||
try:
|
||||
result = loading.instantiate_class(
|
||||
result = await loading.instantiate_class(
|
||||
node_type=self.vertex_type,
|
||||
base_type=self.base_type,
|
||||
params=self.params,
|
||||
|
|
@ -358,9 +356,9 @@ class Vertex:
|
|||
|
||||
raise ValueError(message)
|
||||
|
||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
if not self._built or force:
|
||||
self._build(user_id, *args, **kwargs)
|
||||
await self._build(user_id, *args, **kwargs)
|
||||
|
||||
return self._built_object
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import ast
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.utils import flatten_list
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
|
||||
|
||||
|
|
@ -34,18 +34,18 @@ class AgentVertex(Vertex):
|
|||
elif isinstance(source_node, ChainVertex):
|
||||
self.chains.append(source_node)
|
||||
|
||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
if not self._built or force:
|
||||
self._set_tools_and_chains()
|
||||
# First, build the tools
|
||||
for tool_node in self.tools:
|
||||
tool_node.build(user_id=user_id)
|
||||
await tool_node.build(user_id=user_id)
|
||||
|
||||
# Next, build the chains and the rest
|
||||
for chain_node in self.chains:
|
||||
chain_node.build(tools=self.tools, user_id=user_id)
|
||||
await chain_node.build(tools=self.tools, user_id=user_id)
|
||||
|
||||
self._build(user_id=user_id)
|
||||
await self._build(user_id=user_id)
|
||||
|
||||
return self._built_object
|
||||
|
||||
|
|
@ -62,13 +62,13 @@ class LLMVertex(Vertex):
|
|||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||
super().__init__(data, base_type="llms", params=params)
|
||||
|
||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
# 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.ß
|
||||
if self.vertex_type == self.built_node_type:
|
||||
return self.class_built_object
|
||||
if not self._built or force:
|
||||
self._build(user_id=user_id)
|
||||
await self._build(user_id=user_id)
|
||||
self.built_node_type = self.vertex_type
|
||||
self.class_built_object = self._built_object
|
||||
# Avoid deepcopying the LLM
|
||||
|
|
@ -90,11 +90,11 @@ class WrapperVertex(Vertex):
|
|||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="wrappers")
|
||||
|
||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||
if not self._built or force:
|
||||
if "headers" in self.params:
|
||||
self.params["headers"] = ast.literal_eval(self.params["headers"])
|
||||
self._build(user_id=user_id)
|
||||
await self._build(user_id=user_id)
|
||||
return self._built_object
|
||||
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ class ChainVertex(Vertex):
|
|||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="chains")
|
||||
|
||||
def build(
|
||||
async def build(
|
||||
self,
|
||||
force: bool = False,
|
||||
user_id=None,
|
||||
|
|
@ -212,9 +212,9 @@ class ChainVertex(Vertex):
|
|||
if isinstance(value, PromptVertex):
|
||||
# Build the PromptVertex, passing the tools if available
|
||||
tools = kwargs.get("tools", None)
|
||||
self.params[key] = value.build(tools=tools, force=force)
|
||||
self.params[key] = await value.build(tools=tools, force=force)
|
||||
|
||||
self._build(user_id=user_id)
|
||||
await self._build(user_id=user_id)
|
||||
|
||||
return self._built_object
|
||||
|
||||
|
|
@ -223,7 +223,7 @@ class PromptVertex(Vertex):
|
|||
def __init__(self, data: Dict):
|
||||
super().__init__(data, base_type="prompts")
|
||||
|
||||
def build(
|
||||
async def build(
|
||||
self,
|
||||
force: bool = False,
|
||||
user_id=None,
|
||||
|
|
@ -236,7 +236,7 @@ class PromptVertex(Vertex):
|
|||
self.params["input_variables"] = []
|
||||
# Check if it is a ZeroShotPrompt and needs a tool
|
||||
if "ShotPrompt" in self.vertex_type:
|
||||
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
|
||||
tools = [await 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
|
||||
# first check if it is a list
|
||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||
|
|
@ -257,7 +257,7 @@ class PromptVertex(Vertex):
|
|||
elif isinstance(self.params, dict):
|
||||
self.params.pop("input_variables", None)
|
||||
|
||||
self._build(user_id=user_id)
|
||||
await self._build(user_id=user_id)
|
||||
return self._built_object
|
||||
|
||||
def _built_object_repr(self):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
from typing import Any, List, Optional
|
||||
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.agents import (
|
||||
AgentExecutor,
|
||||
Tool,
|
||||
ZeroShotAgent,
|
||||
initialize_agent,
|
||||
AgentType,
|
||||
)
|
||||
from langchain.agents import AgentExecutor, AgentType, Tool, ZeroShotAgent, initialize_agent
|
||||
from langchain.agents.agent_toolkits import (
|
||||
SQLDatabaseToolkit,
|
||||
VectorStoreInfo,
|
||||
|
|
@ -16,25 +9,18 @@ from langchain.agents.agent_toolkits import (
|
|||
)
|
||||
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
|
||||
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
|
||||
from langchain_experimental.agents.agent_toolkits.pandas.prompt import (
|
||||
PREFIX as PANDAS_PREFIX,
|
||||
)
|
||||
from langchain_experimental.agents.agent_toolkits.pandas.prompt import (
|
||||
SUFFIX_WITH_DF as PANDAS_SUFFIX,
|
||||
)
|
||||
from langchain.agents.agent_toolkits.sql.prompt import SQL_PREFIX, SQL_SUFFIX
|
||||
from langchain.agents.agent_toolkits.vectorstore.prompt import (
|
||||
PREFIX as VECTORSTORE_PREFIX,
|
||||
)
|
||||
from langchain.agents.agent_toolkits.vectorstore.prompt import (
|
||||
ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX,
|
||||
)
|
||||
from langchain.agents.agent_toolkits.vectorstore.prompt import PREFIX as VECTORSTORE_PREFIX
|
||||
from langchain.agents.agent_toolkits.vectorstore.prompt import ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX
|
||||
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.chains.llm import LLMChain
|
||||
from langchain.memory.chat_memory import BaseChatMemory
|
||||
from langchain.sql_database import SQLDatabase
|
||||
from langchain_experimental.tools.python.tool import PythonAstREPLTool
|
||||
from langchain.tools.sql_database.prompt import QUERY_CHECKER
|
||||
from langchain_experimental.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX
|
||||
from langchain_experimental.agents.agent_toolkits.pandas.prompt import SUFFIX_WITH_DF as PANDAS_SUFFIX
|
||||
from langchain_experimental.tools.python.tool import PythonAstREPLTool
|
||||
from langflow.interface.base import CustomAgentExecutor
|
||||
|
||||
|
||||
|
|
@ -55,7 +41,7 @@ class JsonAgent(CustomAgentExecutor):
|
|||
@classmethod
|
||||
def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel):
|
||||
tools = toolkit if isinstance(toolkit, list) else toolkit.get_tools()
|
||||
tool_names = {tool.name for tool in tools}
|
||||
tool_names = list({tool.name for tool in tools})
|
||||
prompt = ZeroShotAgent.create_prompt(
|
||||
tools,
|
||||
prefix=JSON_PREFIX,
|
||||
|
|
@ -112,7 +98,7 @@ class CSVAgent(CustomAgentExecutor):
|
|||
llm=llm,
|
||||
prompt=partial_prompt,
|
||||
)
|
||||
tool_names = {tool.name for tool in tools}
|
||||
tool_names = list({tool.name for tool in tools})
|
||||
agent = ZeroShotAgent(
|
||||
llm_chain=llm_chain,
|
||||
allowed_tools=tool_names,
|
||||
|
|
@ -151,7 +137,7 @@ class VectorStoreAgent(CustomAgentExecutor):
|
|||
llm=llm,
|
||||
prompt=prompt,
|
||||
)
|
||||
tool_names = {tool.name for tool in tools}
|
||||
tool_names = list({tool.name for tool in tools})
|
||||
agent = ZeroShotAgent(
|
||||
llm_chain=llm_chain,
|
||||
allowed_tools=tool_names,
|
||||
|
|
@ -217,7 +203,7 @@ class SQLAgent(CustomAgentExecutor):
|
|||
llm=llm,
|
||||
prompt=prompt,
|
||||
)
|
||||
tool_names = {tool.name for tool in tools} # type: ignore
|
||||
tool_names = list({tool.name for tool in tools}) # type: ignore
|
||||
agent = ZeroShotAgent(
|
||||
llm_chain=llm_chain,
|
||||
allowed_tools=tool_names,
|
||||
|
|
@ -266,7 +252,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
|||
llm=llm,
|
||||
prompt=prompt,
|
||||
)
|
||||
tool_names = {tool.name for tool in tools}
|
||||
tool_names = list({tool.name for tool in tools})
|
||||
agent = ZeroShotAgent(
|
||||
llm_chain=llm_chain,
|
||||
allowed_tools=tool_names,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import ast
|
||||
import inspect
|
||||
import operator
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Type, Union
|
||||
|
||||
from cachetools import TTLCache, cachedmethod, keys
|
||||
from fastapi import HTTPException
|
||||
|
||||
from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails
|
||||
|
||||
|
||||
|
|
@ -17,6 +20,13 @@ def get_data_type():
|
|||
return Data
|
||||
|
||||
|
||||
def imports_key(*args, **kwargs):
|
||||
imports = kwargs.pop("imports")
|
||||
key = keys.methodkey(*args, **kwargs)
|
||||
key += tuple(imports)
|
||||
return key
|
||||
|
||||
|
||||
class CodeParser:
|
||||
"""
|
||||
A parser for Python source code, extracting code details.
|
||||
|
|
@ -26,6 +36,7 @@ class CodeParser:
|
|||
"""
|
||||
Initializes the parser with the provided code.
|
||||
"""
|
||||
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||
if isinstance(code, type):
|
||||
if not inspect.isclass(code):
|
||||
raise ValueError("The provided code must be a class.")
|
||||
|
|
@ -101,13 +112,14 @@ class CodeParser:
|
|||
arg_dict["type"] = ast.unparse(arg.annotation)
|
||||
return arg_dict
|
||||
|
||||
def construct_eval_env(self, return_type_str: str) -> dict:
|
||||
@cachedmethod(operator.attrgetter("cache"))
|
||||
def construct_eval_env(self, return_type_str: str, imports) -> dict:
|
||||
"""
|
||||
Constructs an evaluation environment with the necessary imports for the return type,
|
||||
taking into account module aliases.
|
||||
"""
|
||||
eval_env = {}
|
||||
for import_entry in self.data["imports"]:
|
||||
eval_env: dict = {}
|
||||
for import_entry in imports:
|
||||
if isinstance(import_entry, tuple): # from module import name
|
||||
module, name = import_entry
|
||||
if name in return_type_str:
|
||||
|
|
@ -122,6 +134,7 @@ class CodeParser:
|
|||
exec(f"import {module} as {alias if alias else module}", eval_env)
|
||||
return eval_env
|
||||
|
||||
@cachedmethod(cache=operator.attrgetter("cache"))
|
||||
def parse_callable_details(self, node: ast.FunctionDef) -> Dict[str, Any]:
|
||||
"""
|
||||
Extracts details from a single function or method node.
|
||||
|
|
@ -129,7 +142,7 @@ class CodeParser:
|
|||
return_type = None
|
||||
if node.returns:
|
||||
return_type_str = ast.unparse(node.returns)
|
||||
eval_env = self.construct_eval_env(return_type_str)
|
||||
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
|
||||
|
||||
try:
|
||||
return_type = eval(return_type_str, eval_env)
|
||||
|
|
@ -266,7 +279,7 @@ class CodeParser:
|
|||
elif isinstance(stmt, ast.AnnAssign):
|
||||
if attr := self.parse_ann_assign(stmt):
|
||||
class_details.attributes.append(attr)
|
||||
elif isinstance(stmt, ast.FunctionDef):
|
||||
elif isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
method, is_init = self.parse_function_def(stmt)
|
||||
if is_init:
|
||||
class_details.init = method
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import ast
|
||||
import operator
|
||||
from typing import Any, ClassVar, Optional
|
||||
|
||||
from cachetools import TTLCache, cachedmethod
|
||||
from fastapi import HTTPException
|
||||
|
||||
from langflow.interface.custom.code_parser import CodeParser
|
||||
from langflow.utils import validate
|
||||
|
||||
|
|
@ -24,9 +25,11 @@ class Component:
|
|||
field_config: dict = {}
|
||||
|
||||
def __init__(self, **data):
|
||||
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||
for key, value in data.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
@cachedmethod(cache=operator.attrgetter("cache"))
|
||||
def get_code_tree(self, code: str):
|
||||
parser = CodeParser(code)
|
||||
return parser.parse_code()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent
|
||||
|
||||
from typing import Optional, List, Dict, Union
|
||||
from langflow.field_typing import (
|
||||
AgentExecutor,
|
||||
BaseChatMemory,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import operator
|
||||
from typing import Any, Callable, ClassVar, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
import yaml
|
||||
from cachetools import TTLCache, cachedmethod
|
||||
from fastapi import HTTPException
|
||||
|
||||
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||
|
|
@ -30,6 +32,7 @@ class CustomComponent(Component):
|
|||
status: Optional[str] = None
|
||||
|
||||
def __init__(self, **data):
|
||||
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||
super().__init__(**data)
|
||||
|
||||
@property
|
||||
|
|
@ -71,33 +74,19 @@ class CustomComponent(Component):
|
|||
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
||||
}
|
||||
raise HTTPException(status_code=400, detail=error_detail)
|
||||
return True
|
||||
|
||||
def is_check_valid(self) -> bool:
|
||||
def validate(self) -> bool:
|
||||
return self._class_template_validation(self.code) if self.code else False
|
||||
|
||||
def get_code_tree(self, code: str):
|
||||
return super().get_code_tree(code)
|
||||
|
||||
@property
|
||||
def get_function_entrypoint_args(self) -> str:
|
||||
if not self.code:
|
||||
return ""
|
||||
tree = self.get_code_tree(self.code)
|
||||
|
||||
component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||
if not component_classes:
|
||||
return ""
|
||||
|
||||
# Assume the first Component class is the one we're interested in
|
||||
component_class = component_classes[0]
|
||||
build_methods = [
|
||||
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
|
||||
]
|
||||
|
||||
if not build_methods:
|
||||
return ""
|
||||
|
||||
build_method = build_methods[0]
|
||||
def get_function_entrypoint_args(self) -> list:
|
||||
build_method = self.get_build_method()
|
||||
if not build_method:
|
||||
return []
|
||||
|
||||
args = build_method["args"]
|
||||
for arg in args:
|
||||
|
|
@ -111,13 +100,13 @@ class CustomComponent(Component):
|
|||
),
|
||||
},
|
||||
)
|
||||
elif not arg.get("type"):
|
||||
elif not arg.get("type") and arg.get("name") != "self":
|
||||
# Set the type to Data
|
||||
arg["type"] = "Data"
|
||||
return args
|
||||
|
||||
@property
|
||||
def get_function_entrypoint_return_type(self) -> List[str]:
|
||||
@cachedmethod(operator.attrgetter("cache"))
|
||||
def get_build_method(self):
|
||||
if not self.code:
|
||||
return []
|
||||
tree = self.get_code_tree(self.code)
|
||||
|
|
@ -135,7 +124,13 @@ class CustomComponent(Component):
|
|||
if not build_methods:
|
||||
return []
|
||||
|
||||
build_method = build_methods[0]
|
||||
return build_methods[0]
|
||||
|
||||
@property
|
||||
def get_function_entrypoint_return_type(self) -> List[Any]:
|
||||
build_method = self.get_build_method()
|
||||
if not build_method:
|
||||
return build_method
|
||||
return_type = build_method["return_type"]
|
||||
if not return_type:
|
||||
return []
|
||||
|
|
@ -156,13 +151,15 @@ class CustomComponent(Component):
|
|||
|
||||
@property
|
||||
def get_main_class_name(self):
|
||||
if not self.code:
|
||||
return ""
|
||||
tree = self.get_code_tree(self.code)
|
||||
|
||||
base_name = self.code_class_base_inheritance
|
||||
method_name = self.function_entrypoint_name
|
||||
|
||||
classes = []
|
||||
for item in tree.get("classes"):
|
||||
for item in tree.get("classes", []):
|
||||
if base_name in item["bases"]:
|
||||
method_names = [method["name"] for method in item["methods"]]
|
||||
if method_name in method_names:
|
||||
|
|
@ -173,11 +170,13 @@ class CustomComponent(Component):
|
|||
|
||||
@property
|
||||
def build_template_config(self):
|
||||
if not self.code:
|
||||
return {}
|
||||
tree = self.get_code_tree(self.code)
|
||||
|
||||
attributes = [
|
||||
main_class["attributes"]
|
||||
for main_class in tree.get("classes")
|
||||
for main_class in tree.get("classes", [])
|
||||
if main_class["name"] == self.get_main_class_name
|
||||
]
|
||||
# Get just the first item
|
||||
|
|
@ -189,7 +188,7 @@ class CustomComponent(Component):
|
|||
def get_function(self):
|
||||
return validate.create_function(self.code, self.function_entrypoint_name)
|
||||
|
||||
def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any:
|
||||
async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any:
|
||||
from langflow.processing.process import build_sorted_vertices, process_tweaks
|
||||
|
||||
db_service = get_db_service()
|
||||
|
|
@ -199,7 +198,7 @@ class CustomComponent(Component):
|
|||
raise ValueError(f"Flow {flow_id} not found")
|
||||
if tweaks:
|
||||
graph_data = process_tweaks(graph_data=graph_data, tweaks=tweaks)
|
||||
return build_sorted_vertices(graph_data, self.user_id)
|
||||
return await build_sorted_vertices(graph_data, self.user_id)
|
||||
|
||||
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
||||
if not self.user_id:
|
||||
|
|
@ -213,7 +212,7 @@ class CustomComponent(Component):
|
|||
except Exception as e:
|
||||
raise ValueError("Session is invalid") from e
|
||||
|
||||
def get_flow(
|
||||
async def get_flow(
|
||||
self,
|
||||
*,
|
||||
flow_name: Optional[str] = None,
|
||||
|
|
@ -233,7 +232,7 @@ class CustomComponent(Component):
|
|||
|
||||
if not flow:
|
||||
raise ValueError(f"Flow {flow_name or flow_id} not found")
|
||||
return self.load_flow(flow.id, tweaks)
|
||||
return await self.load_flow(flow.id, tweaks)
|
||||
|
||||
def build(self, *args: Any, **kwargs: Any) -> Any:
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ def extract_inner_type_from_generic_alias(return_type: GenericAlias) -> Any:
|
|||
return return_type
|
||||
|
||||
|
||||
def extract_union_types_from_generic_alias(return_type: GenericAlias) -> tuple:
|
||||
def extract_union_types_from_generic_alias(return_type: GenericAlias) -> list:
|
||||
"""
|
||||
Extracts the inner type from a type hint that is a Union.
|
||||
"""
|
||||
return return_type.__args__
|
||||
return list(return_type.__args__)
|
||||
|
||||
|
||||
def extract_union_types(return_type: str) -> list[str]:
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
import importlib
|
||||
from typing import Any, Type
|
||||
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain.agents import Agent
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chat_models.base import BaseChatModel
|
||||
from langchain.prompts import PromptTemplate
|
||||
from langchain.tools import BaseTool
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.utils import validate
|
||||
from langflow.interface.wrappers.base import wrapper_creator
|
||||
from langflow.utils import validate
|
||||
|
||||
|
||||
def import_module(module_path: str) -> Any:
|
||||
|
|
@ -180,6 +180,7 @@ def get_function(code):
|
|||
return validate.create_function(code, function_name)
|
||||
|
||||
|
||||
def get_function_custom(code):
|
||||
def eval_custom_component_code(code: str) -> Type[CustomComponent]:
|
||||
"""Evaluate custom component code"""
|
||||
class_name = validate.extract_class_name(code)
|
||||
return validate.create_class(code, class_name)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import inspect
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
|
||||
|
||||
|
|
@ -11,7 +12,7 @@ from langchain.document_loaders.base import BaseLoader
|
|||
from langchain.schema import Document
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
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.importing.utils import eval_custom_component_code, get_function, import_by_type
|
||||
from langflow.interface.initialize.llm import initialize_vertexai
|
||||
from langflow.interface.initialize.utils import handle_format_kwargs, handle_node_type, handle_partial_variables
|
||||
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||
|
|
@ -35,7 +36,7 @@ def build_vertex_in_params(params: Dict) -> Dict:
|
|||
return {key: value.build() if isinstance(value, Vertex) else value for key, value in params.items()}
|
||||
|
||||
|
||||
def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None) -> Any:
|
||||
async def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None) -> Any:
|
||||
"""Instantiate class from module type and key, and params"""
|
||||
params = convert_params_to_sets(params)
|
||||
params = convert_kwargs(params)
|
||||
|
|
@ -47,7 +48,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None
|
|||
return custom_node(**params)
|
||||
logger.debug(f"Instantiating {node_type} of type {base_type}")
|
||||
class_object = import_by_type(_type=base_type, name=node_type)
|
||||
return instantiate_based_on_type(class_object, base_type, node_type, params, user_id=user_id)
|
||||
return await instantiate_based_on_type(class_object, base_type, node_type, params, user_id=user_id)
|
||||
|
||||
|
||||
def convert_params_to_sets(params):
|
||||
|
|
@ -74,7 +75,7 @@ def convert_kwargs(params):
|
|||
return params
|
||||
|
||||
|
||||
def instantiate_based_on_type(class_object, base_type, node_type, params, user_id):
|
||||
async def instantiate_based_on_type(class_object, base_type, node_type, params, user_id):
|
||||
if base_type == "agents":
|
||||
return instantiate_agent(node_type, class_object, params)
|
||||
elif base_type == "prompts":
|
||||
|
|
@ -108,20 +109,28 @@ def instantiate_based_on_type(class_object, base_type, node_type, params, user_i
|
|||
elif base_type == "memory":
|
||||
return instantiate_memory(node_type, class_object, params)
|
||||
elif base_type == "custom_components":
|
||||
return instantiate_custom_component(node_type, class_object, params, user_id)
|
||||
return await instantiate_custom_component(node_type, class_object, params, user_id)
|
||||
elif base_type == "wrappers":
|
||||
return instantiate_wrapper(node_type, class_object, params)
|
||||
else:
|
||||
return class_object(**params)
|
||||
|
||||
|
||||
def instantiate_custom_component(node_type, class_object, params, user_id):
|
||||
# we need to make a copy of the params because we will be
|
||||
# modifying it
|
||||
async def instantiate_custom_component(node_type, class_object, params, user_id):
|
||||
params_copy = params.copy()
|
||||
class_object: "CustomComponent" = get_function_custom(params_copy.pop("code"))
|
||||
class_object: "CustomComponent" = eval_custom_component_code(params_copy.pop("code"))
|
||||
custom_component = class_object(user_id=user_id)
|
||||
built_object = custom_component.build(**params_copy)
|
||||
|
||||
# Determine if the build method is asynchronous
|
||||
is_async = inspect.iscoroutinefunction(custom_component.build)
|
||||
|
||||
if is_async:
|
||||
# Await the build method directly if it's async
|
||||
built_object = await custom_component.build(**params_copy)
|
||||
else:
|
||||
# Call the build method directly if it's sync
|
||||
built_object = custom_component.build(**params_copy)
|
||||
|
||||
return built_object, {"repr": custom_component.custom_repr()}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
from typing import Dict, Tuple, Optional, Union
|
||||
from langflow.graph import Graph
|
||||
from loguru import logger
|
||||
from typing import Dict, Optional, Tuple, Union
|
||||
from uuid import UUID
|
||||
|
||||
from loguru import logger
|
||||
|
||||
def build_sorted_vertices(data_graph, user_id: Optional[Union[str, UUID]] = None) -> Tuple[Graph, Dict]:
|
||||
from langflow.graph import Graph
|
||||
|
||||
|
||||
async def build_sorted_vertices(data_graph, user_id: Optional[Union[str, UUID]] = None) -> Tuple[Graph, Dict]:
|
||||
"""
|
||||
Build langchain object from data_graph.
|
||||
"""
|
||||
|
|
@ -14,28 +16,12 @@ def build_sorted_vertices(data_graph, user_id: Optional[Union[str, UUID]] = None
|
|||
sorted_vertices = graph.topological_sort()
|
||||
artifacts = {}
|
||||
for vertex in sorted_vertices:
|
||||
vertex.build(user_id=user_id)
|
||||
await vertex.build(user_id=user_id)
|
||||
if vertex.artifacts:
|
||||
artifacts.update(vertex.artifacts)
|
||||
return graph, artifacts
|
||||
|
||||
|
||||
def build_langchain_object(data_graph):
|
||||
"""
|
||||
Build langchain object from data_graph.
|
||||
"""
|
||||
|
||||
logger.debug("Building langchain object")
|
||||
nodes = data_graph["nodes"]
|
||||
# Add input variables
|
||||
# nodes = payload.extract_input_variables(nodes)
|
||||
# Nodes, edges and root node
|
||||
edges = data_graph["edges"]
|
||||
graph = Graph(nodes, edges)
|
||||
|
||||
return graph.build()
|
||||
|
||||
|
||||
def get_memory_key(langchain_object):
|
||||
"""
|
||||
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import warnings
|
|||
from typing import Any, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from cachetools import LRUCache, cached
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from langflow.api.utils import get_new_key
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
|
|
@ -16,7 +19,7 @@ from langflow.interface.custom.directory_reader import DirectoryReader
|
|||
from langflow.interface.custom.utils import extract_inner_type
|
||||
from langflow.interface.document_loaders.base import documentloader_creator
|
||||
from langflow.interface.embeddings.base import embedding_creator
|
||||
from langflow.interface.importing.utils import get_function_custom
|
||||
from langflow.interface.importing.utils import eval_custom_component_code
|
||||
from langflow.interface.llms.base import llm_creator
|
||||
from langflow.interface.memories.base import memory_creator
|
||||
from langflow.interface.output_parsers.base import output_parser_creator
|
||||
|
|
@ -32,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.custom_components import CustomComponentFrontendNode
|
||||
from langflow.utils.util import get_base_classes
|
||||
from loguru import logger
|
||||
|
||||
|
||||
# Used to get the base_classes list
|
||||
|
|
@ -48,6 +50,7 @@ def get_type_list():
|
|||
return all_types
|
||||
|
||||
|
||||
@cached(LRUCache(maxsize=1))
|
||||
def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
|
||||
"""Build a dictionary of all langchain types"""
|
||||
all_types = {}
|
||||
|
|
@ -202,9 +205,14 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio
|
|||
"""Build the field configuration for a custom component"""
|
||||
|
||||
try:
|
||||
custom_class = get_function_custom(custom_component.code)
|
||||
if custom_component.code is None:
|
||||
return {}
|
||||
elif isinstance(custom_component.code, str):
|
||||
custom_class = eval_custom_component_code(custom_component.code)
|
||||
else:
|
||||
raise ValueError("Invalid code type")
|
||||
except Exception as exc:
|
||||
logger.error(f"Error while getting custom function: {str(exc)}")
|
||||
logger.error(f"Error while evaluating custom component code: {str(exc)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
|
|
@ -228,7 +236,7 @@ def build_field_config(custom_component: CustomComponent, user_id: Optional[Unio
|
|||
|
||||
def add_extra_fields(frontend_node, field_config, function_args):
|
||||
"""Add extra fields to the frontend node"""
|
||||
if function_args is None or function_args == "":
|
||||
if not function_args:
|
||||
return
|
||||
|
||||
# sort function_args which is a list of dicts
|
||||
|
|
@ -368,7 +376,7 @@ def build_valid_menu(valid_components):
|
|||
for menu_item in valid_components["menu"]:
|
||||
menu_name = menu_item["name"]
|
||||
valid_menu[menu_name] = {}
|
||||
|
||||
menu_path = menu_item["path"]
|
||||
for component in menu_item["components"]:
|
||||
logger.debug(f"Building component: {component.get('name'), component.get('output_types')}")
|
||||
try:
|
||||
|
|
@ -377,10 +385,12 @@ def build_valid_menu(valid_components):
|
|||
component_output_types = component["output_types"]
|
||||
|
||||
component_extractor = CustomComponent(code=component_code)
|
||||
component_extractor.is_check_valid()
|
||||
component_extractor.validate()
|
||||
|
||||
component_template = build_langchain_template_custom_component(component_extractor)
|
||||
component_template["output_types"] = component_output_types
|
||||
full_path = f"{menu_path}/{component.get('file')}"
|
||||
component_template["full_path"] = full_path
|
||||
if len(component_output_types) == 1:
|
||||
component_name = component_output_types[0]
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ from fastapi import FastAPI, Request
|
|||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from langflow.api import router
|
||||
from langflow.interface.utils import setup_llm_caching
|
||||
from langflow.services.plugins.langfuse import LangfuseInstance
|
||||
|
|
@ -32,7 +31,7 @@ def create_app():
|
|||
|
||||
@app.middleware("http")
|
||||
async def flatten_query_string_lists(request: Request, call_next):
|
||||
flattened = []
|
||||
flattened: list[tuple[str, str]] = []
|
||||
for key, value in request.query_params.multi_items():
|
||||
flattened.extend((key, entry) for entry in value.split(","))
|
||||
|
||||
|
|
@ -100,7 +99,6 @@ def setup_app(static_files_dir: Optional[Path] = None, backend_only: bool = Fals
|
|||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
from langflow.__main__ import get_number_of_workers
|
||||
|
||||
configure()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,15 @@
|
|||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from langchain.schema import AgentAction
|
||||
from langflow.interface.run import (
|
||||
build_sorted_vertices,
|
||||
get_memory_key,
|
||||
update_memory_keys,
|
||||
)
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.schema import AgentAction, Document
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
from langflow.graph import Graph
|
||||
from langflow.interface.run import build_sorted_vertices, get_memory_key, update_memory_keys
|
||||
from langflow.services.deps import get_session_service
|
||||
from loguru import logger
|
||||
from langflow.graph import Graph
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.vectorstores.base import VectorStore
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from langchain.schema import Document
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
|
|
@ -164,8 +160,8 @@ async def process_graph_cached(
|
|||
if session_id is None:
|
||||
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
|
||||
# Load the graph using SessionService
|
||||
graph, artifacts = session_service.load_session(session_id, data_graph)
|
||||
built_object = graph.build()
|
||||
graph, artifacts = await session_service.load_session(session_id, data_graph)
|
||||
built_object = await graph.build()
|
||||
processed_inputs = process_inputs(inputs, artifacts)
|
||||
result = generate_result(built_object, processed_inputs)
|
||||
# langchain_object is now updated with the new memory
|
||||
|
|
@ -202,7 +198,7 @@ def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = N
|
|||
graph = Graph(nodes, edges)
|
||||
|
||||
if build:
|
||||
langchain_object = graph.build()
|
||||
langchain_object = asyncio.run(graph.build())
|
||||
|
||||
if hasattr(langchain_object, "verbose"):
|
||||
langchain_object.verbose = True
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ from cryptography.fernet import Fernet
|
|||
from fastapi import Depends, HTTPException, Security, status
|
||||
from fastapi.security import APIKeyHeader, APIKeyQuery, OAuth2PasswordBearer
|
||||
from jose import JWTError, jwt
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.services.database.models.api_key.api_key import ApiKey
|
||||
from langflow.services.database.models.api_key.crud import check_key
|
||||
from langflow.services.database.models.user.crud import get_user_by_id, get_user_by_username, update_user_last_login_at
|
||||
from langflow.services.database.models.user.user import User
|
||||
from langflow.services.deps import get_session, get_settings_service
|
||||
from sqlmodel import Session
|
||||
|
||||
oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login", auto_error=False)
|
||||
|
||||
|
|
@ -323,6 +324,8 @@ def decrypt_api_key(encrypted_api_key: str, settings_service=Depends(get_setting
|
|||
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()
|
||||
encoded_bytes = encrypted_api_key.encode()
|
||||
else:
|
||||
encoded_bytes = encrypted_api_key
|
||||
decrypted_key = fernet.decrypt(encoded_bytes).decode()
|
||||
return decrypted_key
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from langflow.interface.run import build_sorted_vertices
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.cache.utils import compute_dict_hash
|
||||
|
|
@ -14,7 +15,7 @@ class SessionService(Service):
|
|||
def __init__(self, cache_service):
|
||||
self.cache_service: "BaseCacheService" = cache_service
|
||||
|
||||
def load_session(self, key, data_graph):
|
||||
async def load_session(self, key, data_graph):
|
||||
# Check if the data is cached
|
||||
if key in self.cache_service:
|
||||
return self.cache_service.get(key)
|
||||
|
|
@ -23,7 +24,7 @@ class SessionService(Service):
|
|||
key = self.generate_key(session_id=None, data_graph=data_graph)
|
||||
|
||||
# If not cached, build the graph and cache it
|
||||
graph, artifacts = build_sorted_vertices(data_graph)
|
||||
graph, artifacts = await build_sorted_vertices(data_graph)
|
||||
|
||||
self.cache_service.set(key, (graph, artifacts))
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ForbiddenError(CustomException):
|
|||
|
||||
class APIKeyError(CustomException):
|
||||
def __init__(self, detail="API key error"):
|
||||
super().__init__(detail, 401)
|
||||
super().__init__(detail, 400) #! Should be 401
|
||||
|
||||
|
||||
class FilterError(CustomException):
|
||||
|
|
|
|||
|
|
@ -445,6 +445,8 @@ class StoreService(Service):
|
|||
|
||||
result: List[ListComponentResponse] = []
|
||||
authorized = False
|
||||
metadata = {}
|
||||
comp_count = 0
|
||||
try:
|
||||
result, metadata = await self.query_components(
|
||||
api_key=store_api_key,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Any, Dict, List
|
||||
|
||||
OPENAI_MODELS = [
|
||||
"text-davinci-003",
|
||||
"text-davinci-002",
|
||||
|
|
@ -59,3 +61,117 @@ DIRECT_TYPES = [
|
|||
"code",
|
||||
"NestedDict",
|
||||
]
|
||||
|
||||
|
||||
LOADERS_INFO: List[Dict[str, Any]] = [
|
||||
{
|
||||
"loader": "AirbyteJSONLoader",
|
||||
"name": "Airbyte JSON (.jsonl)",
|
||||
"import": "langchain.document_loaders.AirbyteJSONLoader",
|
||||
"defaultFor": ["jsonl"],
|
||||
"allowdTypes": ["jsonl"],
|
||||
},
|
||||
{
|
||||
"loader": "JSONLoader",
|
||||
"name": "JSON (.json)",
|
||||
"import": "langchain.document_loaders.JSONLoader",
|
||||
"defaultFor": ["json"],
|
||||
"allowdTypes": ["json"],
|
||||
},
|
||||
{
|
||||
"loader": "BSHTMLLoader",
|
||||
"name": "BeautifulSoup4 HTML (.html, .htm)",
|
||||
"import": "langchain.document_loaders.BSHTMLLoader",
|
||||
"allowdTypes": ["html", "htm"],
|
||||
},
|
||||
{
|
||||
"loader": "CSVLoader",
|
||||
"name": "CSV (.csv)",
|
||||
"import": "langchain.document_loaders.CSVLoader",
|
||||
"defaultFor": ["csv"],
|
||||
"allowdTypes": ["csv"],
|
||||
},
|
||||
{
|
||||
"loader": "CoNLLULoader",
|
||||
"name": "CoNLL-U (.conllu)",
|
||||
"import": "langchain.document_loaders.CoNLLULoader",
|
||||
"defaultFor": ["conllu"],
|
||||
"allowdTypes": ["conllu"],
|
||||
},
|
||||
{
|
||||
"loader": "EverNoteLoader",
|
||||
"name": "EverNote (.enex)",
|
||||
"import": "langchain.document_loaders.EverNoteLoader",
|
||||
"defaultFor": ["enex"],
|
||||
"allowdTypes": ["enex"],
|
||||
},
|
||||
{
|
||||
"loader": "FacebookChatLoader",
|
||||
"name": "Facebook Chat (.json)",
|
||||
"import": "langchain.document_loaders.FacebookChatLoader",
|
||||
"allowdTypes": ["json"],
|
||||
},
|
||||
{
|
||||
"loader": "OutlookMessageLoader",
|
||||
"name": "Outlook Message (.msg)",
|
||||
"import": "langchain.document_loaders.OutlookMessageLoader",
|
||||
"defaultFor": ["msg"],
|
||||
"allowdTypes": ["msg"],
|
||||
},
|
||||
{
|
||||
"loader": "PyPDFLoader",
|
||||
"name": "PyPDF (.pdf)",
|
||||
"import": "langchain.document_loaders.PyPDFLoader",
|
||||
"defaultFor": ["pdf"],
|
||||
"allowdTypes": ["pdf"],
|
||||
},
|
||||
{
|
||||
"loader": "STRLoader",
|
||||
"name": "Subtitle (.str)",
|
||||
"import": "langchain.document_loaders.STRLoader",
|
||||
"defaultFor": ["str"],
|
||||
"allowdTypes": ["str"],
|
||||
},
|
||||
{
|
||||
"loader": "TextLoader",
|
||||
"name": "Text (.txt)",
|
||||
"import": "langchain.document_loaders.TextLoader",
|
||||
"defaultFor": ["txt"],
|
||||
"allowdTypes": ["txt"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredEmailLoader",
|
||||
"name": "Unstructured Email (.eml)",
|
||||
"import": "langchain.document_loaders.UnstructuredEmailLoader",
|
||||
"defaultFor": ["eml"],
|
||||
"allowdTypes": ["eml"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredHTMLLoader",
|
||||
"name": "Unstructured HTML (.html, .htm)",
|
||||
"import": "langchain.document_loaders.UnstructuredHTMLLoader",
|
||||
"defaultFor": ["html", "htm"],
|
||||
"allowdTypes": ["html", "htm"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredMarkdownLoader",
|
||||
"name": "Unstructured Markdown (.md)",
|
||||
"import": "langchain.document_loaders.UnstructuredMarkdownLoader",
|
||||
"defaultFor": ["md"],
|
||||
"allowdTypes": ["md"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredPowerPointLoader",
|
||||
"name": "Unstructured PowerPoint (.pptx)",
|
||||
"import": "langchain.document_loaders.UnstructuredPowerPointLoader",
|
||||
"defaultFor": ["pptx"],
|
||||
"allowdTypes": ["pptx"],
|
||||
},
|
||||
{
|
||||
"loader": "UnstructuredWordLoader",
|
||||
"name": "Unstructured Word (.docx)",
|
||||
"import": "langchain.document_loaders.UnstructuredWordLoader",
|
||||
"defaultFor": ["docx"],
|
||||
"allowdTypes": ["docx"],
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -145,16 +145,46 @@ def create_function(code, function_name):
|
|||
|
||||
|
||||
def create_class(code, class_name):
|
||||
"""
|
||||
Dynamically create a class from a string of code and a specified class name.
|
||||
|
||||
:param code: String containing the Python code defining the class
|
||||
:param class_name: Name of the class to be created
|
||||
:return: A function that, when called, returns an instance of the created class
|
||||
"""
|
||||
if not hasattr(ast, "TypeIgnore"):
|
||||
|
||||
class TypeIgnore(ast.AST):
|
||||
_fields = ()
|
||||
|
||||
ast.TypeIgnore = TypeIgnore
|
||||
ast.TypeIgnore = create_type_ignore_class()
|
||||
|
||||
module = ast.parse(code)
|
||||
exec_globals = globals().copy()
|
||||
exec_globals = prepare_global_scope(module)
|
||||
|
||||
class_code = extract_class_code(module, class_name)
|
||||
compiled_class = compile_class_code(class_code)
|
||||
|
||||
return build_class_constructor(compiled_class, exec_globals, class_name)
|
||||
|
||||
|
||||
def create_type_ignore_class():
|
||||
"""
|
||||
Create a TypeIgnore class for AST module if it doesn't exist.
|
||||
|
||||
:return: TypeIgnore class
|
||||
"""
|
||||
|
||||
class TypeIgnore(ast.AST):
|
||||
_fields = ()
|
||||
|
||||
return TypeIgnore
|
||||
|
||||
|
||||
def prepare_global_scope(module):
|
||||
"""
|
||||
Prepares the global scope with necessary imports from the provided code module.
|
||||
|
||||
:param module: AST parsed module
|
||||
:return: Dictionary representing the global scope with imported modules
|
||||
"""
|
||||
exec_globals = globals().copy()
|
||||
for node in module.body:
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
|
|
@ -169,17 +199,47 @@ def create_class(code, class_name):
|
|||
exec_globals[alias.name] = getattr(imported_module, alias.name)
|
||||
except ModuleNotFoundError as e:
|
||||
raise ModuleNotFoundError(f"Module {node.module} not found. Please install it and try again.") from e
|
||||
return exec_globals
|
||||
|
||||
|
||||
def extract_class_code(module, class_name):
|
||||
"""
|
||||
Extracts the AST node for the specified class from the module.
|
||||
|
||||
:param module: AST parsed module
|
||||
:param class_name: Name of the class to extract
|
||||
:return: AST node of the specified class
|
||||
"""
|
||||
class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name)
|
||||
class_code.parent = None
|
||||
return class_code
|
||||
|
||||
|
||||
def compile_class_code(class_code):
|
||||
"""
|
||||
Compiles the AST node of a class into a code object.
|
||||
|
||||
:param class_code: AST node of the class
|
||||
:return: Compiled code object of the class
|
||||
"""
|
||||
code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec")
|
||||
# This suppresses import errors
|
||||
# with contextlib.suppress(Exception):
|
||||
exec(code_obj, exec_globals, locals())
|
||||
return code_obj
|
||||
|
||||
|
||||
def build_class_constructor(compiled_class, exec_globals, class_name):
|
||||
"""
|
||||
Builds a constructor function for the dynamically created class.
|
||||
|
||||
:param compiled_class: Compiled code object of the class
|
||||
:param exec_globals: Global scope with necessary imports
|
||||
:param class_name: Name of the class
|
||||
:return: Constructor function for the class
|
||||
"""
|
||||
exec(compiled_class, exec_globals, locals())
|
||||
exec_globals[class_name] = locals()[class_name]
|
||||
|
||||
# Return a function that imports necessary modules and creates an instance of the target class
|
||||
def build_my_class(*args, **kwargs):
|
||||
def build_custom_class(*args, **kwargs):
|
||||
for module_name, module in exec_globals.items():
|
||||
if isinstance(module, type(importlib)):
|
||||
globals()[module_name] = module
|
||||
|
|
@ -187,9 +247,13 @@ def create_class(code, class_name):
|
|||
instance = exec_globals[class_name](*args, **kwargs)
|
||||
return instance
|
||||
|
||||
build_my_class.__globals__.update(exec_globals)
|
||||
build_custom_class.__globals__.update(exec_globals)
|
||||
return build_custom_class
|
||||
|
||||
return build_my_class
|
||||
|
||||
# Example usage:
|
||||
# class_builder = create_class("class MyClass: pass", "MyClass")
|
||||
# my_instance = class_builder()
|
||||
|
||||
|
||||
def extract_function_name(code):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from celery.exceptions import SoftTimeLimitExceeded # type: ignore
|
||||
|
||||
from langflow.core.celery_app import celery_app
|
||||
from langflow.processing.process import (
|
||||
Result,
|
||||
generate_result,
|
||||
process_inputs,
|
||||
)
|
||||
from langflow.processing.process import Result, generate_result, process_inputs
|
||||
from langflow.services.deps import get_session_service
|
||||
from langflow.services.manager import initialize_session_service
|
||||
|
||||
|
|
@ -27,7 +23,7 @@ def build_vertex(self, vertex: "Vertex") -> "Vertex":
|
|||
"""
|
||||
try:
|
||||
vertex.task_id = self.request.id
|
||||
vertex.build()
|
||||
async_to_sync(vertex.build)()
|
||||
return vertex
|
||||
except SoftTimeLimitExceeded as e:
|
||||
raise self.retry(exc=SoftTimeLimitExceeded("Task took too long"), countdown=2) from e
|
||||
|
|
@ -47,7 +43,7 @@ def process_graph_cached_task(
|
|||
if session_id is None:
|
||||
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
|
||||
# Load the graph using SessionService
|
||||
graph, artifacts = session_service.load_session(session_id, data_graph)
|
||||
graph, artifacts = async_to_sync(session_service.load_session)(session_id, data_graph)
|
||||
built_object = graph.build()
|
||||
processed_inputs = process_inputs(inputs, artifacts)
|
||||
result = generate_result(built_object, processed_inputs)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { FlowType } from "../../types/flow";
|
|||
import { removeApiKeys } from "../../utils/reactflowUtils";
|
||||
import { getTagsIds } from "../../utils/storeUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
import { StoreContext } from "../../contexts/storeContext";
|
||||
|
||||
export default function ShareModal({
|
||||
component,
|
||||
|
|
@ -22,14 +23,17 @@ export default function ShareModal({
|
|||
children,
|
||||
open,
|
||||
setOpen,
|
||||
disabled
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
is_component: boolean;
|
||||
component: FlowType;
|
||||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}): JSX.Element {
|
||||
const { version, addFlow } = useContext(FlowsContext);
|
||||
const {hasApiKey} = useContext(StoreContext)
|
||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [name, setName] = useState(component?.name ?? "");
|
||||
|
|
@ -46,10 +50,12 @@ export default function ShareModal({
|
|||
|
||||
useEffect(() => {
|
||||
if (open || internalOpen) {
|
||||
handleGetTags();
|
||||
handleGetNames();
|
||||
if(hasApiKey){
|
||||
handleGetTags();
|
||||
handleGetNames();
|
||||
}
|
||||
}
|
||||
}, [open, internalOpen]);
|
||||
}, [open, internalOpen,hasApiKey]);
|
||||
|
||||
function handleGetTags() {
|
||||
setLoadingTags(true);
|
||||
|
|
@ -113,7 +119,7 @@ export default function ShareModal({
|
|||
return (
|
||||
<BaseModal
|
||||
size="smaller-h-full"
|
||||
open={open ?? internalOpen}
|
||||
open={(!disabled && open) ?? internalOpen}
|
||||
setOpen={setOpen ?? internalSetOpen}
|
||||
>
|
||||
<BaseModal.Trigger>{children ? children : <></>}</BaseModal.Trigger>
|
||||
|
|
|
|||
|
|
@ -322,9 +322,9 @@ export default function Page({
|
|||
);
|
||||
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance!.project({
|
||||
x: event.clientX - reactflowBounds!.left,
|
||||
y: event.clientY - reactflowBounds!.top,
|
||||
const position = reactFlowInstance!.screenToFlowPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
});
|
||||
|
||||
// Generate a unique node ID
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ import {
|
|||
} from "../../../../utils/utils";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
|
||||
export default function ExtraSidebar(): JSX.Element {
|
||||
const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } =
|
||||
useContext(typesContext);
|
||||
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
|
||||
useContext(FlowsContext);
|
||||
const {hasApiKey} = useContext(StoreContext)
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const [dataFilter, setFilterData] = useState(data);
|
||||
const [search, setSearch] = useState("");
|
||||
|
|
@ -48,6 +50,8 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
event.dataTransfer.setData("nodedata", JSON.stringify(data));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handle showing components after use search input
|
||||
function handleSearchInput(e: string) {
|
||||
if (e === "") {
|
||||
|
|
@ -180,7 +184,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
const ModalMemo = useMemo(
|
||||
() => (
|
||||
<ShareModal is_component={false} component={flow!}>
|
||||
<ShareModal is_component={false} component={flow!} disabled={!hasApiKey}>
|
||||
<ShadTooltip content="Share" side="top">
|
||||
<div className={classNames("extra-side-bar-buttons")}>
|
||||
<IconComponent name="Share2" className="side-bar-button-size" />
|
||||
|
|
@ -188,16 +192,16 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</ShadTooltip>
|
||||
</ShareModal>
|
||||
),
|
||||
[]
|
||||
[hasApiKey]
|
||||
);
|
||||
|
||||
const ExportMemo = useMemo(
|
||||
() => (
|
||||
<ExportModal>
|
||||
<ShadTooltip content="Export" side="top">
|
||||
<div className={classNames("extra-side-bar-buttons")}>
|
||||
<button disabled={!hasApiKey} className={classNames("extra-side-bar-buttons")}>
|
||||
<IconComponent name="FileDown" className="side-bar-button-size" />
|
||||
</div>
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
</ExportModal>
|
||||
),
|
||||
|
|
@ -305,7 +309,15 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
<div className="side-bar-components-div-arrangement">
|
||||
{Object.keys(dataFilter)
|
||||
.sort()
|
||||
.sort((a, b) => {
|
||||
if (a.toLowerCase() === "saved_components") {
|
||||
return -1;
|
||||
} else if (b.toLowerCase() === "saved_components") {
|
||||
return 1;
|
||||
} else {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
})
|
||||
.map((SBSectionName: keyof APIObjectType, index) =>
|
||||
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
|
||||
<DisclosureComponent
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ export default function SidebarDraggableComponent({
|
|||
>
|
||||
<span className="side-bar-components-text">{display_name}</span>
|
||||
<div>
|
||||
<SelectTrigger>
|
||||
<IconComponent
|
||||
<IconComponent
|
||||
name="Menu"
|
||||
className="side-bar-components-icon "
|
||||
/>
|
||||
<SelectTrigger>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={"download"}>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
|
||||
export default function NodeToolbarComponent({
|
||||
data,
|
||||
|
|
@ -50,6 +51,7 @@ export default function NodeToolbarComponent({
|
|||
);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { getNodeId } = useContext(FlowsContext);
|
||||
const {hasApiKey} = useContext(StoreContext)
|
||||
|
||||
function canMinimize() {
|
||||
let countHandles: number = 0;
|
||||
|
|
@ -87,7 +89,7 @@ export default function NodeToolbarComponent({
|
|||
downloadNode(createFlowComponent(cloneDeep(data), version));
|
||||
break;
|
||||
case "Share":
|
||||
setShowconfirmShare(true);
|
||||
if(hasApiKey) setShowconfirmShare(true);
|
||||
break;
|
||||
case "SaveAll":
|
||||
saveComponent(cloneDeep(data));
|
||||
|
|
@ -211,7 +213,7 @@ export default function NodeToolbarComponent({
|
|||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
<SelectItem value={"Share"}>
|
||||
{hasApiKey && <SelectItem value={"Share"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Share2"
|
||||
|
|
@ -219,7 +221,7 @@ export default function NodeToolbarComponent({
|
|||
/>{" "}
|
||||
Share{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
</SelectItem>}
|
||||
<SelectItem value={"Download"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
import ast
|
||||
import pytest
|
||||
import types
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from langflow.services.database.models.flow import Flow, FlowCreate
|
||||
from langflow.field_typing.constants import Data
|
||||
from langflow.interface.custom.base import CustomComponent
|
||||
from langflow.interface.custom.code_parser import CodeParser, CodeSyntaxError
|
||||
from langflow.interface.custom.component import (
|
||||
Component,
|
||||
ComponentCodeNullError,
|
||||
)
|
||||
from langflow.interface.custom.code_parser import CodeParser, CodeSyntaxError
|
||||
|
||||
from langflow.services.database.models.flow import Flow, FlowCreate
|
||||
|
||||
code_default = """
|
||||
from langflow import Prompt
|
||||
|
|
@ -229,9 +228,11 @@ def test_custom_component_get_function_entrypoint_return_type():
|
|||
Test the get_function_entrypoint_return_type
|
||||
property of the CustomComponent class.
|
||||
"""
|
||||
from langchain.schema import Document
|
||||
|
||||
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
|
||||
return_type = custom_component.get_function_entrypoint_return_type
|
||||
assert return_type == ["Document"]
|
||||
assert return_type == [Document]
|
||||
|
||||
|
||||
def test_custom_component_get_main_class_name():
|
||||
|
|
@ -414,7 +415,7 @@ class MyClass(CustomComponent):
|
|||
|
||||
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
|
||||
return_type = custom_component.get_function_entrypoint_return_type
|
||||
assert return_type == []
|
||||
assert return_type == [Data]
|
||||
|
||||
|
||||
def test_custom_component_get_main_class_name_no_main_class():
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
import copy
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
from pathlib import Path
|
||||
from typing import Type, Union
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langchain.agents import AgentExecutor
|
||||
|
||||
import pytest
|
||||
from langchain.agents import AgentExecutor
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.llms.fake import FakeListLLM
|
||||
from langflow.graph import Graph
|
||||
from langflow.graph.vertex.types import (
|
||||
FileToolVertex,
|
||||
LLMVertex,
|
||||
ToolkitVertex,
|
||||
)
|
||||
from langflow.processing.process import get_result_and_thought
|
||||
from langflow.utils.payload import get_root_node
|
||||
from langflow.graph.edge.base import Edge
|
||||
from langflow.graph.graph.utils import (
|
||||
find_last_node,
|
||||
process_flow,
|
||||
set_new_target_handle,
|
||||
ungroup_node,
|
||||
process_flow,
|
||||
update_source_handle,
|
||||
update_target_handle,
|
||||
update_template,
|
||||
)
|
||||
from langflow.graph.utils import UnbuiltObject
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
from langflow.graph.vertex.types import FileToolVertex, LLMVertex, ToolkitVertex
|
||||
from langflow.processing.process import get_result_and_thought
|
||||
from langflow.utils.payload import get_root_node
|
||||
|
||||
# Test cases for the graph module
|
||||
|
||||
|
|
@ -232,29 +230,32 @@ def test_build_params(basic_graph):
|
|||
assert "memory" in root.params
|
||||
|
||||
|
||||
def test_build(basic_graph):
|
||||
@pytest.mark.asyncio
|
||||
async def test_build(basic_graph):
|
||||
"""Test Node's build method"""
|
||||
assert_agent_was_built(basic_graph)
|
||||
await assert_agent_was_built(basic_graph)
|
||||
|
||||
|
||||
def assert_agent_was_built(graph):
|
||||
async def assert_agent_was_built(graph):
|
||||
"""Assert that the agent was built"""
|
||||
assert isinstance(graph, Graph)
|
||||
# Now we test the build method
|
||||
# Build the Agent
|
||||
result = graph.build()
|
||||
result = await graph.build()
|
||||
# The agent should be a AgentExecutor
|
||||
assert isinstance(result, Chain)
|
||||
|
||||
|
||||
def test_llm_node_build(basic_graph):
|
||||
@pytest.mark.asyncio
|
||||
async def test_llm_node_build(basic_graph):
|
||||
llm_node = get_node_by_type(basic_graph, LLMVertex)
|
||||
assert llm_node is not None
|
||||
built_object = llm_node.build()
|
||||
assert built_object is not None
|
||||
built_object = await llm_node.build()
|
||||
assert built_object is not UnbuiltObject()
|
||||
|
||||
|
||||
def test_toolkit_node_build(client, openapi_graph):
|
||||
@pytest.mark.asyncio
|
||||
async def test_toolkit_node_build(client, openapi_graph):
|
||||
# Write a file to the disk
|
||||
file_path = "api-with-examples.yaml"
|
||||
with open(file_path, "w") as f:
|
||||
|
|
@ -262,36 +263,31 @@ def test_toolkit_node_build(client, openapi_graph):
|
|||
|
||||
toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
|
||||
assert toolkit_node is not None
|
||||
built_object = toolkit_node.build()
|
||||
assert built_object is not None
|
||||
built_object = await toolkit_node.build()
|
||||
assert built_object is not UnbuiltObject
|
||||
# Remove the file
|
||||
os.remove(file_path)
|
||||
assert not Path(file_path).exists()
|
||||
|
||||
|
||||
def test_file_tool_node_build(client, openapi_graph):
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_tool_node_build(client, openapi_graph):
|
||||
file_path = "api-with-examples.yaml"
|
||||
with open(file_path, "w") as f:
|
||||
f.write("openapi: 3.0.0")
|
||||
|
||||
assert Path(file_path).exists()
|
||||
file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
|
||||
assert file_tool_node is not None
|
||||
built_object = file_tool_node.build()
|
||||
assert built_object is not None
|
||||
assert file_tool_node is not UnbuiltObject
|
||||
built_object = await file_tool_node.build()
|
||||
assert built_object is not UnbuiltObject
|
||||
# Remove the file
|
||||
os.remove(file_path)
|
||||
assert not Path(file_path).exists()
|
||||
|
||||
|
||||
# def test_wrapper_node_build(openapi_graph):
|
||||
# wrapper_node = get_node_by_type(openapi_graph, WrapperVertex)
|
||||
# assert wrapper_node is not None
|
||||
# built_object = wrapper_node.build()
|
||||
# assert built_object is not None
|
||||
|
||||
|
||||
def test_get_result_and_thought(basic_graph):
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_result_and_thought(basic_graph):
|
||||
"""Test the get_result_and_thought method"""
|
||||
responses = [
|
||||
"Final Answer: I am a response",
|
||||
|
|
@ -303,7 +299,7 @@ def test_get_result_and_thought(basic_graph):
|
|||
assert llm_node is not None
|
||||
llm_node._built_object = FakeListLLM(responses=responses)
|
||||
llm_node._built = True
|
||||
langchain_object = basic_graph.build()
|
||||
langchain_object = await basic_graph.build()
|
||||
# assert all nodes are built
|
||||
assert all(node._built for node in basic_graph.nodes)
|
||||
# now build again and check if FakeListLLM was used
|
||||
|
|
@ -486,27 +482,29 @@ def test_update_source_handle():
|
|||
assert updated_edge["data"]["sourceHandle"]["id"] == "last_node"
|
||||
|
||||
|
||||
def test_pickle_graph(json_vector_store):
|
||||
@pytest.mark.asyncio
|
||||
async def test_pickle_graph(json_vector_store):
|
||||
loaded_json = json.loads(json_vector_store)
|
||||
graph = Graph.from_payload(loaded_json)
|
||||
assert isinstance(graph, Graph)
|
||||
first_result = graph.build()
|
||||
first_result = await graph.build()
|
||||
assert isinstance(first_result, AgentExecutor)
|
||||
pickled = pickle.dumps(graph)
|
||||
assert pickled is not None
|
||||
assert pickled is not UnbuiltObject
|
||||
unpickled = pickle.loads(pickled)
|
||||
assert unpickled is not None
|
||||
result = unpickled.build()
|
||||
assert unpickled is not UnbuiltObject
|
||||
result = await unpickled.build()
|
||||
assert isinstance(result, AgentExecutor)
|
||||
|
||||
|
||||
def test_pickle_each_vertex(json_vector_store):
|
||||
@pytest.mark.asyncio
|
||||
async def test_pickle_each_vertex(json_vector_store):
|
||||
loaded_json = json.loads(json_vector_store)
|
||||
graph = Graph.from_payload(loaded_json)
|
||||
assert isinstance(graph, Graph)
|
||||
for vertex in graph.nodes:
|
||||
vertex.build()
|
||||
await vertex.build()
|
||||
pickled = pickle.dumps(vertex)
|
||||
assert pickled is not None
|
||||
assert pickled is not UnbuiltObject
|
||||
unpickled = pickle.loads(pickled)
|
||||
assert unpickled is not None
|
||||
assert unpickled is not UnbuiltObject
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import pytest
|
||||
from langflow.processing.process import process_tweaks
|
||||
from langflow.services.deps import get_session_service
|
||||
|
||||
|
|
@ -197,39 +198,42 @@ def test_tweak_not_in_template():
|
|||
assert result == graph_data
|
||||
|
||||
|
||||
def test_load_langchain_object_with_cached_session(client, basic_graph_data):
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_langchain_object_with_cached_session(client, basic_graph_data):
|
||||
# Provide a non-existent session_id
|
||||
session_service = get_session_service()
|
||||
session_id1 = "non-existent-session-id"
|
||||
graph1, artifacts1 = session_service.load_session(session_id1, basic_graph_data)
|
||||
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
# Use the new session_id to get the langchain_object again
|
||||
graph2, artifacts2 = session_service.load_session(session_id1, basic_graph_data)
|
||||
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
|
||||
assert graph1 == graph2
|
||||
assert artifacts1 == artifacts2
|
||||
|
||||
|
||||
def test_load_langchain_object_with_no_cached_session(client, basic_graph_data):
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_langchain_object_with_no_cached_session(client, basic_graph_data):
|
||||
# Provide a non-existent session_id
|
||||
session_service = get_session_service()
|
||||
session_id1 = "non-existent-session-id"
|
||||
session_id = session_service.build_key(session_id1, basic_graph_data)
|
||||
graph1, artifacts1 = session_service.load_session(session_id, basic_graph_data)
|
||||
graph1, artifacts1 = await session_service.load_session(session_id, basic_graph_data)
|
||||
# Clear the cache
|
||||
session_service.clear_session(session_id)
|
||||
# Use the new session_id to get the langchain_object again
|
||||
graph2, artifacts2 = session_service.load_session(session_id, basic_graph_data)
|
||||
graph2, artifacts2 = await session_service.load_session(session_id, basic_graph_data)
|
||||
|
||||
assert id(graph1) != id(graph2)
|
||||
# Since the cache was cleared, objects should be different
|
||||
|
||||
|
||||
def test_load_langchain_object_without_session_id(client, basic_graph_data):
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_langchain_object_without_session_id(client, basic_graph_data):
|
||||
# Provide a non-existent session_id
|
||||
session_service = get_session_service()
|
||||
session_id1 = None
|
||||
graph1, artifacts1 = session_service.load_session(session_id1, basic_graph_data)
|
||||
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
# Use the new session_id to get the langchain_object again
|
||||
graph2, artifacts2 = session_service.load_session(session_id1, basic_graph_data)
|
||||
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
|
||||
assert graph1 == graph2
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue