Merge branch 'feature/store' of github.com:logspace-ai/langflow into feature/store

This commit is contained in:
cristhianzl 2023-11-23 18:41:25 -03:00
commit 9e653f2bdc
39 changed files with 641 additions and 481 deletions

108
poetry.lock generated
View file

@ -411,17 +411,17 @@ files = [
[[package]] [[package]]
name = "boto3" name = "boto3"
version = "1.29.5" version = "1.29.6"
description = "The AWS SDK for Python" description = "The AWS SDK for Python"
optional = false optional = false
python-versions = ">= 3.7" python-versions = ">= 3.7"
files = [ files = [
{file = "boto3-1.29.5-py3-none-any.whl", hash = "sha256:030b0f0faf8d44f97e67a5411644243482f33ebf1c45338bb40662239a16dda4"}, {file = "boto3-1.29.6-py3-none-any.whl", hash = "sha256:f4d19e01d176c3a5a05e4af733185ff1891b08a3c38d4a439800fa132aa6e9be"},
{file = "boto3-1.29.5.tar.gz", hash = "sha256:76fc6a17781c27558c526e899579ccf530df10eb279261fe7800540f0043917e"}, {file = "boto3-1.29.6.tar.gz", hash = "sha256:d1d0d979a70bf9b0b13ae3b017f8523708ad953f62d16f39a602d67ee9b25554"},
] ]
[package.dependencies] [package.dependencies]
botocore = ">=1.32.5,<1.33.0" botocore = ">=1.32.6,<1.33.0"
jmespath = ">=0.7.1,<2.0.0" jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.7.0,<0.8.0" s3transfer = ">=0.7.0,<0.8.0"
@ -430,13 +430,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]] [[package]]
name = "botocore" name = "botocore"
version = "1.32.5" version = "1.32.6"
description = "Low-level, data-driven core of boto 3." description = "Low-level, data-driven core of boto 3."
optional = false optional = false
python-versions = ">= 3.7" python-versions = ">= 3.7"
files = [ files = [
{file = "botocore-1.32.5-py3-none-any.whl", hash = "sha256:b8960c955ba275915bf022c54c896c2dac1038289d8a5ace92d1431257c0a439"}, {file = "botocore-1.32.6-py3-none-any.whl", hash = "sha256:4454f967a4d1a01e3e6205c070455bc4e8fd53b5b0753221581ae679c55a9dfd"},
{file = "botocore-1.32.5.tar.gz", hash = "sha256:75a68f942cd87baff83b3a20dfda11b3aeda48aad32e4dcd6fe8992c0cb0e7db"}, {file = "botocore-1.32.6.tar.gz", hash = "sha256:ecec876103783b5efe6099762dda60c2af67e45f7c0ab4568e8265d11c6c449b"},
] ]
[package.dependencies] [package.dependencies]
@ -3488,13 +3488,13 @@ zookeeper = ["kazoo (>=2.8.0)"]
[[package]] [[package]]
name = "langchain" name = "langchain"
version = "0.0.339" version = "0.0.340"
description = "Building applications with LLMs through composability" description = "Building applications with LLMs through composability"
optional = false optional = false
python-versions = ">=3.8.1,<4.0" python-versions = ">=3.8.1,<4.0"
files = [ files = [
{file = "langchain-0.0.339-py3-none-any.whl", hash = "sha256:fec250074a6fbb3711a51423d830006d69f34aedb67604df39c642be80852cbb"}, {file = "langchain-0.0.340-py3-none-any.whl", hash = "sha256:f80f40b52ef82424e38e894db8b8048b6505da100679e72613316f8d8b0243fb"},
{file = "langchain-0.0.339.tar.gz", hash = "sha256:34eb4d7987d979663e361da435479c6f0648a170dae3eb1e9f0f7417f033a2c1"}, {file = "langchain-0.0.340.tar.gz", hash = "sha256:1a6bd2511bbb81e42d2a3d7291ee03de180accab851181ee9fdbb7fbaef6c57c"},
] ]
[package.dependencies] [package.dependencies]
@ -3559,13 +3559,13 @@ six = "*"
[[package]] [[package]]
name = "langfuse" name = "langfuse"
version = "1.7.4" version = "1.7.5"
description = "A client library for accessing langfuse" description = "A client library for accessing langfuse"
optional = false optional = false
python-versions = ">=3.8.1,<4.0" python-versions = ">=3.8.1,<4.0"
files = [ files = [
{file = "langfuse-1.7.4-py3-none-any.whl", hash = "sha256:f5f1e19eac2d01e9854f567f0946f47dac3be59ee40f335e616355b3545018f3"}, {file = "langfuse-1.7.5-py3-none-any.whl", hash = "sha256:ebbcc52f454a9c7cfc9f382e66fddafddb0219f9233598317bbcb66c215b39b6"},
{file = "langfuse-1.7.4.tar.gz", hash = "sha256:5813d2f43e7ba106ae58f048d81c7091fd681be73b35d87d53ac321f999738ae"}, {file = "langfuse-1.7.5.tar.gz", hash = "sha256:99fc5a30b157a16cc3dcb82e84af13fabc2fd0d192be32ef2ad6d9a7fe27d130"},
] ]
[package.dependencies] [package.dependencies]
@ -3936,13 +3936,13 @@ typing-extensions = "*"
[[package]] [[package]]
name = "metaphor-python" name = "metaphor-python"
version = "0.1.20" version = "0.1.21"
description = "A Python package for the Metaphor API." description = "A Python package for the Metaphor API."
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "metaphor-python-0.1.20.tar.gz", hash = "sha256:a1ee7a3b21ff8644553a73bc08a4d475abed182d9a1a0a72c729910837081d50"}, {file = "metaphor-python-0.1.21.tar.gz", hash = "sha256:72604c45c7bf447613f9cdf713c6c57612f1790ead2e78b13c65588e5d7aa279"},
{file = "metaphor_python-0.1.20-py3-none-any.whl", hash = "sha256:ac06b5bf86f6fb1b2371b8be6589766da8eccd6876d56e94b738654dea9adc9a"}, {file = "metaphor_python-0.1.21-py3-none-any.whl", hash = "sha256:b17099f6c37e26a77fbb77a242163fa6b64aad487687264f4ec6c9d16665c5a8"},
] ]
[package.dependencies] [package.dependencies]
@ -4169,38 +4169,38 @@ dill = ">=0.3.7"
[[package]] [[package]]
name = "mypy" name = "mypy"
version = "1.7.0" version = "1.7.1"
description = "Optional static typing for Python" description = "Optional static typing for Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"}, {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"},
{file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"}, {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"},
{file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"}, {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"},
{file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"}, {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"},
{file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"}, {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"},
{file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"}, {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"},
{file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"}, {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"},
{file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"}, {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"},
{file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"}, {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"},
{file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"}, {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"},
{file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"}, {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"},
{file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"}, {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"},
{file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"}, {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"},
{file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"}, {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"},
{file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"}, {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"},
{file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"}, {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"},
{file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"}, {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"},
{file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"}, {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"},
{file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"}, {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"},
{file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"}, {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"},
{file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"}, {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"},
{file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"}, {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"},
{file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"}, {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"},
{file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"}, {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"},
{file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"}, {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"},
{file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"}, {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"},
{file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"}, {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"},
] ]
[package.dependencies] [package.dependencies]
@ -6143,6 +6143,24 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 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]] [[package]]
name = "pytest-cov" name = "pytest-cov"
version = "4.1.0" version = "4.1.0"
@ -9078,4 +9096,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.9,<3.11" python-versions = ">=3.9,<3.11"
content-hash = "8335fd767d4ff476b3caf2a9e05fcce1f353d4b3a2e181585080a618099669fa" content-hash = "62e47482eefda134f0801744360624549d7024861380f51f280f60450d768615"

View file

@ -104,6 +104,7 @@ qianfan = "0.0.5"
pgvector = "^0.2.3" pgvector = "^0.2.3"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
pytest-asyncio = "^0.21.1"
types-redis = "^4.6.0.5" types-redis = "^4.6.0.5"
ipykernel = "^6.21.2" ipykernel = "^6.21.2"
mypy = "^1.1.1" mypy = "^1.1.1"

View file

@ -8,21 +8,20 @@ from fastapi import (
status, status,
) )
from fastapi.responses import StreamingResponse 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.utils import build_input_keys_response
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
from langflow.services.auth.utils import ( from langflow.services.auth.utils import (
get_current_active_user, get_current_active_user,
get_current_user_by_jwt, 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.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"]) router = APIRouter(tags=["Chat"])
@ -164,9 +163,9 @@ async def stream_build(
} }
yield str(StreamData(event="log", data=log_dict)) yield str(StreamData(event="log", data=log_dict))
if vertex.is_task: if vertex.is_task:
vertex = try_running_celery_task(vertex, user_id) vertex = await try_running_celery_task(vertex, user_id)
else: else:
vertex.build(user_id=user_id) await vertex.build(user_id=user_id)
params = vertex._built_object_repr() params = vertex._built_object_repr()
valid = True valid = True
logger.debug(f"Building node {str(vertex.vertex_type)}") logger.debug(f"Building node {str(vertex.vertex_type)}")
@ -193,7 +192,7 @@ async def stream_build(
yield str(StreamData(event="message", data=response)) 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 # Now we need to check the input_keys to send them to the client
if hasattr(langchain_object, "input_keys"): if hasattr(langchain_object, "input_keys"):
input_keys_response = build_input_keys_response(langchain_object, artifacts) 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)) 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 # Try running the task in celery
# and set the task_id to the local vertex # and set the task_id to the local vertex
# if it fails, run the task locally # if it fails, run the task locally
@ -236,5 +235,5 @@ def try_running_celery_task(vertex, user_id):
except Exception as exc: except Exception as exc:
logger.debug(f"Error running task in celery: {exc}") logger.debug(f"Error running task in celery: {exc}")
vertex.task_id = None vertex.task_id = None
vertex.build(user_id=user_id) await vertex.build(user_id=user_id)
return vertex return vertex

View file

@ -1,33 +1,30 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Annotated, Optional, Union 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 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 ( from langflow.api.v1.schemas import (
CustomComponentCode,
ProcessResponse, ProcessResponse,
TaskResponse, TaskResponse,
TaskStatusResponse, TaskStatusResponse,
UploadFileResponse, UploadFileResponse,
CustomComponentCode,
) )
from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.services.deps import get_session 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: try:
from langflow.worker import process_graph_cached_task from langflow.worker import process_graph_cached_task
@ -39,7 +36,6 @@ except ImportError:
from sqlmodel import Session from sqlmodel import Session
from langflow.services.task.service import TaskService from langflow.services.task.service import TaskService
# build router # build router
@ -217,6 +213,25 @@ async def custom_component(
) )
extractor = CustomComponent(code=raw_code.code) extractor = CustomComponent(code=raw_code.code)
extractor.is_check_valid() extractor.validate()
return build_langchain_template_custom_component(extractor, user_id=user.id) 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))

View 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,
)

View file

@ -1,17 +1,15 @@
from langflow import CustomComponent from typing import List, Optional
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 langchain.agents.agent import AgentExecutor 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.agents.openai_functions_agent.base import OpenAIFunctionsAgent
from langchain.chat_models import ChatOpenAI
from langchain.memory.token_buffer import ConversationTokenBufferMemory from langchain.memory.token_buffer import ConversationTokenBufferMemory
from langchain.prompts import SystemMessagePromptTemplate
from langchain.prompts.chat import MessagesPlaceholder from langchain.prompts.chat import MessagesPlaceholder
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import ( from langchain.schema.memory import BaseMemory
_get_default_system_message, from langchain.tools import Tool
) from langflow import CustomComponent
class ConversationalAgent(CustomComponent): class ConversationalAgent(CustomComponent):
@ -27,7 +25,7 @@ class ConversationalAgent(CustomComponent):
"gpt-4-32k", "gpt-4-32k",
] ]
return { return {
"tools": {"is_list": True, "display_name": "Tools"}, "tools": {"display_name": "Tools"},
"memory": {"display_name": "Memory"}, "memory": {"display_name": "Memory"},
"system_message": {"display_name": "System Message"}, "system_message": {"display_name": "System Message"},
"max_token_limit": {"display_name": "Max Token Limit"}, "max_token_limit": {"display_name": "Max Token Limit"},
@ -43,7 +41,7 @@ class ConversationalAgent(CustomComponent):
self, self,
model_name: str, model_name: str,
openai_api_key: str, openai_api_key: str,
tools: Tool, tools: List[Tool],
openai_api_base: Optional[str] = None, openai_api_base: Optional[str] = None,
memory: Optional[BaseMemory] = None, memory: Optional[BaseMemory] = None,
system_message: Optional[SystemMessagePromptTemplate] = None, system_message: Optional[SystemMessagePromptTemplate] = None,
@ -51,8 +49,8 @@ class ConversationalAgent(CustomComponent):
) -> AgentExecutor: ) -> AgentExecutor:
llm = ChatOpenAI( llm = ChatOpenAI(
model=model_name, model=model_name,
openai_api_key=openai_api_key, api_key=openai_api_key,
openai_api_base=openai_api_base, base_url=openai_api_base,
) )
if not memory: if not memory:
memory_key = "chat_history" memory_key = "chat_history"

View file

@ -1,119 +1,7 @@
from langflow import CustomComponent
from langchain.schema import Document from langchain.schema import Document
from typing import Any, Dict, List
loaders_info: List[Dict[str, Any]] = [ from langflow import CustomComponent
{ from langflow.utils.constants import LOADERS_INFO
"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"],
},
]
class FileLoaderComponent(CustomComponent): class FileLoaderComponent(CustomComponent):
@ -122,12 +10,12 @@ class FileLoaderComponent(CustomComponent):
beta = True beta = True
def build_config(self): 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 = [] file_types = []
suffixes = [] suffixes = []
for loader_info in loaders_info: for loader_info in LOADERS_INFO:
if "allowedTypes" in loader_info: if "allowedTypes" in loader_info:
file_types.extend(loader_info["allowedTypes"]) file_types.extend(loader_info["allowedTypes"])
suffixes.extend([f".{ext}" for ext in 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 # Mapeie o nome do loader selecionado para suas informações
selected_loader_info = None selected_loader_info = None
for loader_info in loaders_info: for loader_info in LOADERS_INFO:
if loader_info["name"] == loader: if loader_info["name"] == loader:
selected_loader_info = loader_info selected_loader_info = loader_info
break break
@ -200,7 +88,7 @@ class FileLoaderComponent(CustomComponent):
if loader == "Automatic": if loader == "Automatic":
# Determine o loader automaticamente com base na extensão do arquivo # Determine o loader automaticamente com base na extensão do arquivo
default_loader_info = None default_loader_info = None
for info in loaders_info: for info in LOADERS_INFO:
if "defaultFor" in info and file_type in info["defaultFor"]: if "defaultFor" in info and file_type in info["defaultFor"]:
default_loader_info = info default_loader_info = info
break break

View file

@ -5,8 +5,6 @@ agents:
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/openapi" documentation: "https://python.langchain.com/docs/modules/agents/toolkits/openapi"
CSVAgent: CSVAgent:
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/csv" documentation: "https://python.langchain.com/docs/modules/agents/toolkits/csv"
AgentInitializer:
documentation: "https://python.langchain.com/docs/modules/agents/agent_types/"
VectorStoreAgent: VectorStoreAgent:
documentation: "" documentation: ""
VectorStoreRouterAgent: VectorStoreRouterAgent:

View file

@ -3,11 +3,12 @@ from typing import Callable, Dict, Union
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader from langchain.document_loaders.base import BaseLoader
from langchain.llms.base import BaseLanguageModel, BaseLLM from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
from langchain.schema import BaseOutputParser, BaseRetriever, Document from langchain.schema import BaseOutputParser, BaseRetriever, Document
from langchain.schema.embeddings import Embeddings from langchain.schema.embeddings import Embeddings
from langchain.schema.language_model import BaseLanguageModel
from langchain.schema.memory import BaseMemory from langchain.schema.memory import BaseMemory
from langchain.text_splitter import TextSplitter from langchain.text_splitter import TextSplitter
from langchain.tools import Tool from langchain.tools import Tool

View file

@ -1,6 +1,8 @@
from typing import Dict, Generator, List, Type, Union from typing import Dict, Generator, List, Type, Union
from langchain.chains.base import Chain from langchain.chains.base import Chain
from loguru import logger
from langflow.graph.edge.base import Edge from langflow.graph.edge.base import Edge
from langflow.graph.graph.constants import lazy_load_vertex_dict from langflow.graph.graph.constants import lazy_load_vertex_dict
from langflow.graph.graph.utils import process_flow 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.graph.vertex.types import FileToolVertex, LLMVertex, ToolkitVertex
from langflow.interface.tools.constants import FILE_TOOLS from langflow.interface.tools.constants import FILE_TOOLS
from langflow.utils import payload from langflow.utils import payload
from loguru import logger
class Graph: class Graph:
@ -116,13 +117,13 @@ class Graph:
connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node] connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node]
return connected_nodes return connected_nodes
def build(self) -> Chain: async def build(self) -> Chain:
"""Builds the graph.""" """Builds the graph."""
# Get root node # Get root node
root_node = payload.get_root_node(self) root_node = payload.get_root_node(self)
if root_node is None: if root_node is None:
raise ValueError("No root node found") raise ValueError("No root node found")
return root_node.build() return await root_node.build()
def topological_sort(self) -> List[Vertex]: def topological_sort(self) -> List[Vertex]:
""" """

View file

@ -1,20 +1,18 @@
import ast import ast
import inspect
import pickle 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.utils import UnbuiltObject
from langflow.graph.vertex.utils import is_basic_type from langflow.graph.vertex.utils import is_basic_type
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
from langflow.interface.listing import lazy_load_dict from langflow.interface.listing import lazy_load_dict
from langflow.utils.constants import DIRECT_TYPES from langflow.utils.constants import DIRECT_TYPES
from loguru import logger
from langflow.utils.util import sync_to_async 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: if TYPE_CHECKING:
from langflow.graph.edge.base import Edge from langflow.graph.edge.base import Edge
@ -216,18 +214,18 @@ class Vertex:
self._raw_params = params self._raw_params = params
self.params = params self.params = params
def _build(self, user_id=None): async def _build(self, user_id=None):
""" """
Initiate the build process. Initiate the build process.
""" """
logger.debug(f"Building {self.vertex_type}") logger.debug(f"Building {self.vertex_type}")
self._build_each_node_in_params_dict(user_id) await self._build_each_node_in_params_dict(user_id)
self._get_and_instantiate_class(user_id) await self._get_and_instantiate_class(user_id)
self._validate_built_object() self._validate_built_object()
self._built = True 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. Iterates over each node in the params dictionary and builds it.
""" """
@ -236,9 +234,9 @@ class Vertex:
if value == self: if value == self:
del self.params[key] del self.params[key]
continue 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): 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): def _is_node(self, value):
""" """
@ -252,7 +250,7 @@ class Vertex:
""" """
return all(self._is_node(node) for node in value) 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 # Check if the Vertex was built already
if self._built: if self._built:
return self._built_object return self._built_object
@ -268,27 +266,27 @@ class Vertex:
pass pass
# If there's no task_id, build the vertex locally # If there's no task_id, build the vertex locally
self.build(user_id) await self.build(user_id)
return self._built_object 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. 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) self._handle_func(key, result)
if isinstance(result, list): if isinstance(result, list):
self._extend_params_list_with_result(key, result) self._extend_params_list_with_result(key, result)
self.params[key] = result self.params[key] = result
def _build_list_of_nodes_and_update_params(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. Iterates over a list of nodes, builds each and updates the params dictionary.
""" """
self.params[key] = [] self.params[key] = []
for node in nodes: for node in nodes:
built = node.get_result(user_id) built = await node.get_result(user_id)
if isinstance(built, list): if isinstance(built, list):
if key not in self.params: if key not in self.params:
self.params[key] = [] self.params[key] = []
@ -318,14 +316,14 @@ class Vertex:
if isinstance(self.params[key], list): if isinstance(self.params[key], list):
self.params[key].extend(result) 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. Gets the class from a dictionary and instantiates it with the params.
""" """
if self.base_type is None: if self.base_type is None:
raise ValueError(f"Base type for node {self.vertex_type} not found") raise ValueError(f"Base type for node {self.vertex_type} not found")
try: try:
result = loading.instantiate_class( result = await loading.instantiate_class(
node_type=self.vertex_type, node_type=self.vertex_type,
base_type=self.base_type, base_type=self.base_type,
params=self.params, params=self.params,
@ -358,9 +356,9 @@ class Vertex:
raise ValueError(message) 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: if not self._built or force:
self._build(user_id, *args, **kwargs) await self._build(user_id, *args, **kwargs)
return self._built_object return self._built_object

View file

@ -1,8 +1,8 @@
import ast import ast
from typing import Any, Dict, List, Optional, Union 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.utils import flatten_list
from langflow.graph.vertex.base import Vertex
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt
@ -34,18 +34,18 @@ class AgentVertex(Vertex):
elif isinstance(source_node, ChainVertex): elif isinstance(source_node, ChainVertex):
self.chains.append(source_node) 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: if not self._built or force:
self._set_tools_and_chains() self._set_tools_and_chains()
# First, build the tools # First, build the tools
for tool_node in self.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 # Next, build the chains and the rest
for chain_node in self.chains: 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 return self._built_object
@ -62,13 +62,13 @@ class LLMVertex(Vertex):
def __init__(self, data: Dict, params: Optional[Dict] = None): def __init__(self, data: Dict, params: Optional[Dict] = None):
super().__init__(data, base_type="llms", params=params) 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 # LLM is different because some models might take up too much memory
# or time to load. So we only load them when we need them.ß # or time to load. So we only load them when we need them.ß
if self.vertex_type == self.built_node_type: if self.vertex_type == self.built_node_type:
return self.class_built_object return self.class_built_object
if not self._built or force: if not self._built or force:
self._build(user_id=user_id) await self._build(user_id=user_id)
self.built_node_type = self.vertex_type self.built_node_type = self.vertex_type
self.class_built_object = self._built_object self.class_built_object = self._built_object
# Avoid deepcopying the LLM # Avoid deepcopying the LLM
@ -90,11 +90,11 @@ class WrapperVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="wrappers") super().__init__(data, base_type="wrappers")
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 not self._built or force:
if "headers" in self.params: if "headers" in self.params:
self.params["headers"] = ast.literal_eval(self.params["headers"]) 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 return self._built_object
@ -193,7 +193,7 @@ class ChainVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="chains") super().__init__(data, base_type="chains")
def build( async def build(
self, self,
force: bool = False, force: bool = False,
user_id=None, user_id=None,
@ -212,9 +212,9 @@ class ChainVertex(Vertex):
if isinstance(value, PromptVertex): if isinstance(value, PromptVertex):
# Build the PromptVertex, passing the tools if available # Build the PromptVertex, passing the tools if available
tools = kwargs.get("tools", None) 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 return self._built_object
@ -223,7 +223,7 @@ class PromptVertex(Vertex):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="prompts") super().__init__(data, base_type="prompts")
def build( async def build(
self, self,
force: bool = False, force: bool = False,
user_id=None, user_id=None,
@ -236,7 +236,7 @@ class PromptVertex(Vertex):
self.params["input_variables"] = [] self.params["input_variables"] = []
# Check if it is a ZeroShotPrompt and needs a tool # Check if it is a ZeroShotPrompt and needs a tool
if "ShotPrompt" in self.vertex_type: if "ShotPrompt" in self.vertex_type:
tools = [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 # flatten the list of tools if it is a list of lists
# first check if it is a list # first check if it is a list
if tools and isinstance(tools, list) and isinstance(tools[0], list): if tools and isinstance(tools, list) and isinstance(tools[0], list):
@ -257,7 +257,7 @@ class PromptVertex(Vertex):
elif isinstance(self.params, dict): elif isinstance(self.params, dict):
self.params.pop("input_variables", None) self.params.pop("input_variables", None)
self._build(user_id=user_id) await self._build(user_id=user_id)
return self._built_object return self._built_object
def _built_object_repr(self): def _built_object_repr(self):

View file

@ -1,13 +1,6 @@
from typing import Any, List, Optional from typing import Any, List, Optional
from langchain.chains.llm import LLMChain from langchain.agents import AgentExecutor, AgentType, Tool, ZeroShotAgent, initialize_agent
from langchain.agents import (
AgentExecutor,
Tool,
ZeroShotAgent,
initialize_agent,
AgentType,
)
from langchain.agents.agent_toolkits import ( from langchain.agents.agent_toolkits import (
SQLDatabaseToolkit, SQLDatabaseToolkit,
VectorStoreInfo, 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.prompt import JSON_PREFIX, JSON_SUFFIX
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit 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.sql.prompt import SQL_PREFIX, SQL_SUFFIX
from langchain.agents.agent_toolkits.vectorstore.prompt import ( from langchain.agents.agent_toolkits.vectorstore.prompt import PREFIX as VECTORSTORE_PREFIX
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 (
ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX,
)
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.base_language import BaseLanguageModel from langchain.base_language import BaseLanguageModel
from langchain.chains.llm import LLMChain
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.sql_database import SQLDatabase 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.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 from langflow.interface.base import CustomAgentExecutor
@ -55,7 +41,7 @@ class JsonAgent(CustomAgentExecutor):
@classmethod @classmethod
def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel): def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel):
tools = toolkit if isinstance(toolkit, list) else toolkit.get_tools() 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( prompt = ZeroShotAgent.create_prompt(
tools, tools,
prefix=JSON_PREFIX, prefix=JSON_PREFIX,
@ -112,7 +98,7 @@ class CSVAgent(CustomAgentExecutor):
llm=llm, llm=llm,
prompt=partial_prompt, prompt=partial_prompt,
) )
tool_names = {tool.name for tool in tools} tool_names = list({tool.name for tool in tools})
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, llm_chain=llm_chain,
allowed_tools=tool_names, allowed_tools=tool_names,
@ -151,7 +137,7 @@ class VectorStoreAgent(CustomAgentExecutor):
llm=llm, llm=llm,
prompt=prompt, prompt=prompt,
) )
tool_names = {tool.name for tool in tools} tool_names = list({tool.name for tool in tools})
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, llm_chain=llm_chain,
allowed_tools=tool_names, allowed_tools=tool_names,
@ -217,7 +203,7 @@ class SQLAgent(CustomAgentExecutor):
llm=llm, llm=llm,
prompt=prompt, 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( agent = ZeroShotAgent(
llm_chain=llm_chain, llm_chain=llm_chain,
allowed_tools=tool_names, allowed_tools=tool_names,
@ -266,7 +252,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
llm=llm, llm=llm,
prompt=prompt, prompt=prompt,
) )
tool_names = {tool.name for tool in tools} tool_names = list({tool.name for tool in tools})
agent = ZeroShotAgent( agent = ZeroShotAgent(
llm_chain=llm_chain, llm_chain=llm_chain,
allowed_tools=tool_names, allowed_tools=tool_names,

View file

@ -1,9 +1,12 @@
import ast import ast
import inspect import inspect
import operator
import traceback import traceback
from typing import Any, Dict, List, Type, Union from typing import Any, Dict, List, Type, Union
from cachetools import TTLCache, cachedmethod, keys
from fastapi import HTTPException from fastapi import HTTPException
from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails
@ -17,6 +20,13 @@ def get_data_type():
return Data return Data
def imports_key(*args, **kwargs):
imports = kwargs.pop("imports")
key = keys.methodkey(*args, **kwargs)
key += tuple(imports)
return key
class CodeParser: class CodeParser:
""" """
A parser for Python source code, extracting code details. A parser for Python source code, extracting code details.
@ -26,6 +36,7 @@ class CodeParser:
""" """
Initializes the parser with the provided code. Initializes the parser with the provided code.
""" """
self.cache = TTLCache(maxsize=1024, ttl=60)
if isinstance(code, type): if isinstance(code, type):
if not inspect.isclass(code): if not inspect.isclass(code):
raise ValueError("The provided code must be a class.") raise ValueError("The provided code must be a class.")
@ -101,13 +112,14 @@ class CodeParser:
arg_dict["type"] = ast.unparse(arg.annotation) arg_dict["type"] = ast.unparse(arg.annotation)
return arg_dict 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, Constructs an evaluation environment with the necessary imports for the return type,
taking into account module aliases. taking into account module aliases.
""" """
eval_env = {} eval_env: dict = {}
for import_entry in self.data["imports"]: for import_entry in imports:
if isinstance(import_entry, tuple): # from module import name if isinstance(import_entry, tuple): # from module import name
module, name = import_entry module, name = import_entry
if name in return_type_str: if name in return_type_str:
@ -122,6 +134,7 @@ class CodeParser:
exec(f"import {module} as {alias if alias else module}", eval_env) exec(f"import {module} as {alias if alias else module}", eval_env)
return eval_env return eval_env
@cachedmethod(cache=operator.attrgetter("cache"))
def parse_callable_details(self, node: ast.FunctionDef) -> Dict[str, Any]: def parse_callable_details(self, node: ast.FunctionDef) -> Dict[str, Any]:
""" """
Extracts details from a single function or method node. Extracts details from a single function or method node.
@ -129,7 +142,7 @@ class CodeParser:
return_type = None return_type = None
if node.returns: if node.returns:
return_type_str = ast.unparse(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: try:
return_type = eval(return_type_str, eval_env) return_type = eval(return_type_str, eval_env)
@ -266,7 +279,7 @@ class CodeParser:
elif isinstance(stmt, ast.AnnAssign): elif isinstance(stmt, ast.AnnAssign):
if attr := self.parse_ann_assign(stmt): if attr := self.parse_ann_assign(stmt):
class_details.attributes.append(attr) 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) method, is_init = self.parse_function_def(stmt)
if is_init: if is_init:
class_details.init = method class_details.init = method

View file

@ -1,8 +1,9 @@
import ast import ast
import operator
from typing import Any, ClassVar, Optional from typing import Any, ClassVar, Optional
from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException from fastapi import HTTPException
from langflow.interface.custom.code_parser import CodeParser from langflow.interface.custom.code_parser import CodeParser
from langflow.utils import validate from langflow.utils import validate
@ -24,9 +25,11 @@ class Component:
field_config: dict = {} field_config: dict = {}
def __init__(self, **data): def __init__(self, **data):
self.cache = TTLCache(maxsize=1024, ttl=60)
for key, value in data.items(): for key, value in data.items():
setattr(self, key, value) setattr(self, key, value)
@cachedmethod(cache=operator.attrgetter("cache"))
def get_code_tree(self, code: str): def get_code_tree(self, code: str):
parser = CodeParser(code) parser = CodeParser(code)
return parser.parse_code() return parser.parse_code()

View file

@ -1,5 +1,5 @@
DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent
from typing import Optional, List, Dict, Union
from langflow.field_typing import ( from langflow.field_typing import (
AgentExecutor, AgentExecutor,
BaseChatMemory, BaseChatMemory,

View file

@ -1,7 +1,9 @@
import operator
from typing import Any, Callable, ClassVar, List, Optional, Union from typing import Any, Callable, ClassVar, List, Optional, Union
from uuid import UUID from uuid import UUID
import yaml import yaml
from cachetools import TTLCache, cachedmethod
from fastapi import HTTPException from fastapi import HTTPException
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
@ -30,6 +32,7 @@ class CustomComponent(Component):
status: Optional[str] = None status: Optional[str] = None
def __init__(self, **data): def __init__(self, **data):
self.cache = TTLCache(maxsize=1024, ttl=60)
super().__init__(**data) super().__init__(**data)
@property @property
@ -71,33 +74,19 @@ class CustomComponent(Component):
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.", "traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
} }
raise HTTPException(status_code=400, detail=error_detail) 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 return self._class_template_validation(self.code) if self.code else False
def get_code_tree(self, code: str): def get_code_tree(self, code: str):
return super().get_code_tree(code) return super().get_code_tree(code)
@property @property
def get_function_entrypoint_args(self) -> str: def get_function_entrypoint_args(self) -> list:
if not self.code: build_method = self.get_build_method()
return "" if not build_method:
tree = self.get_code_tree(self.code) return []
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]
args = build_method["args"] args = build_method["args"]
for arg in 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 # Set the type to Data
arg["type"] = "Data" arg["type"] = "Data"
return args return args
@property @cachedmethod(operator.attrgetter("cache"))
def get_function_entrypoint_return_type(self) -> List[str]: def get_build_method(self):
if not self.code: if not self.code:
return [] return []
tree = self.get_code_tree(self.code) tree = self.get_code_tree(self.code)
@ -135,7 +124,13 @@ class CustomComponent(Component):
if not build_methods: if not build_methods:
return [] 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"] return_type = build_method["return_type"]
if not return_type: if not return_type:
return [] return []
@ -156,13 +151,15 @@ class CustomComponent(Component):
@property @property
def get_main_class_name(self): def get_main_class_name(self):
if not self.code:
return ""
tree = self.get_code_tree(self.code) tree = self.get_code_tree(self.code)
base_name = self.code_class_base_inheritance base_name = self.code_class_base_inheritance
method_name = self.function_entrypoint_name method_name = self.function_entrypoint_name
classes = [] classes = []
for item in tree.get("classes"): for item in tree.get("classes", []):
if base_name in item["bases"]: if base_name in item["bases"]:
method_names = [method["name"] for method in item["methods"]] method_names = [method["name"] for method in item["methods"]]
if method_name in method_names: if method_name in method_names:
@ -173,11 +170,13 @@ class CustomComponent(Component):
@property @property
def build_template_config(self): def build_template_config(self):
if not self.code:
return {}
tree = self.get_code_tree(self.code) tree = self.get_code_tree(self.code)
attributes = [ attributes = [
main_class["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 if main_class["name"] == self.get_main_class_name
] ]
# Get just the first item # Get just the first item
@ -189,7 +188,7 @@ class CustomComponent(Component):
def get_function(self): def get_function(self):
return validate.create_function(self.code, self.function_entrypoint_name) return validate.create_function(self.code, self.function_entrypoint_name)
def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any: async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any:
from langflow.processing.process import build_sorted_vertices, process_tweaks from langflow.processing.process import build_sorted_vertices, process_tweaks
db_service = get_db_service() db_service = get_db_service()
@ -199,7 +198,7 @@ class CustomComponent(Component):
raise ValueError(f"Flow {flow_id} not found") raise ValueError(f"Flow {flow_id} not found")
if tweaks: if tweaks:
graph_data = process_tweaks(graph_data=graph_data, tweaks=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]: def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
if not self.user_id: if not self.user_id:
@ -213,7 +212,7 @@ class CustomComponent(Component):
except Exception as e: except Exception as e:
raise ValueError("Session is invalid") from e raise ValueError("Session is invalid") from e
def get_flow( async def get_flow(
self, self,
*, *,
flow_name: Optional[str] = None, flow_name: Optional[str] = None,
@ -233,7 +232,7 @@ class CustomComponent(Component):
if not flow: if not flow:
raise ValueError(f"Flow {flow_name or flow_id} not found") 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: def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError raise NotImplementedError

View file

@ -22,11 +22,11 @@ def extract_inner_type_from_generic_alias(return_type: GenericAlias) -> Any:
return return_type 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. 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]: def extract_union_types(return_type: str) -> list[str]:

View file

@ -3,15 +3,15 @@
import importlib import importlib
from typing import Any, Type from typing import Any, Type
from langchain.prompts import PromptTemplate
from langchain.agents import Agent from langchain.agents import Agent
from langchain.base_language import BaseLanguageModel from langchain.base_language import BaseLanguageModel
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.prompts import PromptTemplate
from langchain.tools import BaseTool from langchain.tools import BaseTool
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.utils import validate
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils import validate
def import_module(module_path: str) -> Any: def import_module(module_path: str) -> Any:
@ -180,6 +180,7 @@ def get_function(code):
return validate.create_function(code, function_name) 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) class_name = validate.extract_class_name(code)
return validate.create_class(code, class_name) return validate.create_class(code, class_name)

View file

@ -1,3 +1,4 @@
import inspect
import json import json
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type 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.schema import Document
from langchain.vectorstores.base import VectorStore from langchain.vectorstores.base import VectorStore
from langflow.interface.custom_lists import CUSTOM_NODES 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.llm import initialize_vertexai
from langflow.interface.initialize.utils import handle_format_kwargs, handle_node_type, handle_partial_variables from langflow.interface.initialize.utils import handle_format_kwargs, handle_node_type, handle_partial_variables
from langflow.interface.initialize.vector_store import vecstore_initializer 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()} 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""" """Instantiate class from module type and key, and params"""
params = convert_params_to_sets(params) params = convert_params_to_sets(params)
params = convert_kwargs(params) params = convert_kwargs(params)
@ -47,7 +48,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None
return custom_node(**params) return custom_node(**params)
logger.debug(f"Instantiating {node_type} of type {base_type}") logger.debug(f"Instantiating {node_type} of type {base_type}")
class_object = import_by_type(_type=base_type, name=node_type) class_object = import_by_type(_type=base_type, name=node_type)
return instantiate_based_on_type(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): def convert_params_to_sets(params):
@ -74,7 +75,7 @@ def convert_kwargs(params):
return 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": if base_type == "agents":
return instantiate_agent(node_type, class_object, params) return instantiate_agent(node_type, class_object, params)
elif base_type == "prompts": 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": elif base_type == "memory":
return instantiate_memory(node_type, class_object, params) return instantiate_memory(node_type, class_object, params)
elif base_type == "custom_components": 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": elif base_type == "wrappers":
return instantiate_wrapper(node_type, class_object, params) return instantiate_wrapper(node_type, class_object, params)
else: else:
return class_object(**params) return class_object(**params)
def instantiate_custom_component(node_type, class_object, params, user_id): async 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
params_copy = params.copy() 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) 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()} return built_object, {"repr": custom_component.custom_repr()}

View file

@ -1,10 +1,12 @@
from typing import Dict, Tuple, Optional, Union from typing import Dict, Optional, Tuple, Union
from langflow.graph import Graph
from loguru import logger
from uuid import UUID 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. 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() sorted_vertices = graph.topological_sort()
artifacts = {} artifacts = {}
for vertex in sorted_vertices: for vertex in sorted_vertices:
vertex.build(user_id=user_id) await vertex.build(user_id=user_id)
if vertex.artifacts: if vertex.artifacts:
artifacts.update(vertex.artifacts) artifacts.update(vertex.artifacts)
return graph, 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): def get_memory_key(langchain_object):
""" """
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute. Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.

View file

@ -6,7 +6,10 @@ import warnings
from typing import Any, List, Optional, Union from typing import Any, List, Optional, Union
from uuid import UUID from uuid import UUID
from cachetools import LRUCache, cached
from fastapi import HTTPException from fastapi import HTTPException
from loguru import logger
from langflow.api.utils import get_new_key from langflow.api.utils import get_new_key
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_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.custom.utils import extract_inner_type
from langflow.interface.document_loaders.base import documentloader_creator from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_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.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator from langflow.interface.memories.base import memory_creator
from langflow.interface.output_parsers.base import output_parser_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.constants import CLASSES_TO_REMOVE
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
from langflow.utils.util import get_base_classes from langflow.utils.util import get_base_classes
from loguru import logger
# Used to get the base_classes list # Used to get the base_classes list
@ -48,6 +50,7 @@ def get_type_list():
return all_types return all_types
@cached(LRUCache(maxsize=1))
def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
"""Build a dictionary of all langchain types""" """Build a dictionary of all langchain types"""
all_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""" """Build the field configuration for a custom component"""
try: 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: 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( raise HTTPException(
status_code=400, status_code=400,
detail={ 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): def add_extra_fields(frontend_node, field_config, function_args):
"""Add extra fields to the frontend node""" """Add extra fields to the frontend node"""
if function_args is None or function_args == "": if not function_args:
return return
# sort function_args which is a list of dicts # 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"]: for menu_item in valid_components["menu"]:
menu_name = menu_item["name"] menu_name = menu_item["name"]
valid_menu[menu_name] = {} valid_menu[menu_name] = {}
menu_path = menu_item["path"]
for component in menu_item["components"]: for component in menu_item["components"]:
logger.debug(f"Building component: {component.get('name'), component.get('output_types')}") logger.debug(f"Building component: {component.get('name'), component.get('output_types')}")
try: try:
@ -377,10 +385,12 @@ def build_valid_menu(valid_components):
component_output_types = component["output_types"] component_output_types = component["output_types"]
component_extractor = CustomComponent(code=component_code) 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 = build_langchain_template_custom_component(component_extractor)
component_template["output_types"] = component_output_types 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: if len(component_output_types) == 1:
component_name = component_output_types[0] component_name = component_output_types[0]
else: else:

View file

@ -6,7 +6,6 @@ from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from langflow.api import router from langflow.api import router
from langflow.interface.utils import setup_llm_caching from langflow.interface.utils import setup_llm_caching
from langflow.services.plugins.langfuse import LangfuseInstance from langflow.services.plugins.langfuse import LangfuseInstance
@ -32,7 +31,7 @@ def create_app():
@app.middleware("http") @app.middleware("http")
async def flatten_query_string_lists(request: Request, call_next): 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(): for key, value in request.query_params.multi_items():
flattened.extend((key, entry) for entry in value.split(",")) 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__": if __name__ == "__main__":
import uvicorn import uvicorn
from langflow.__main__ import get_number_of_workers from langflow.__main__ import get_number_of_workers
configure() configure()

View file

@ -1,19 +1,15 @@
import asyncio
import json import json
from pathlib import Path from pathlib import Path
from langchain.schema import AgentAction from typing import Any, Dict, List, Optional, Tuple, Union
from langflow.interface.run import (
build_sorted_vertices, from langchain.chains.base import Chain
get_memory_key, from langchain.schema import AgentAction, Document
update_memory_keys, 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 langflow.services.deps import get_session_service
from loguru import logger 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 from pydantic import BaseModel
@ -164,8 +160,8 @@ async def process_graph_cached(
if session_id is None: if session_id is None:
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph) session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
# Load the graph using SessionService # Load the graph using SessionService
graph, artifacts = session_service.load_session(session_id, data_graph) graph, artifacts = await session_service.load_session(session_id, data_graph)
built_object = graph.build() built_object = await graph.build()
processed_inputs = process_inputs(inputs, artifacts) processed_inputs = process_inputs(inputs, artifacts)
result = generate_result(built_object, processed_inputs) result = generate_result(built_object, processed_inputs)
# langchain_object is now updated with the new memory # 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) graph = Graph(nodes, edges)
if build: if build:
langchain_object = graph.build() langchain_object = asyncio.run(graph.build())
if hasattr(langchain_object, "verbose"): if hasattr(langchain_object, "verbose"):
langchain_object.verbose = True langchain_object.verbose = True

View file

@ -6,12 +6,13 @@ from cryptography.fernet import Fernet
from fastapi import Depends, HTTPException, Security, status from fastapi import Depends, HTTPException, Security, status
from fastapi.security import APIKeyHeader, APIKeyQuery, OAuth2PasswordBearer from fastapi.security import APIKeyHeader, APIKeyQuery, OAuth2PasswordBearer
from jose import JWTError, jwt 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.api_key import ApiKey
from langflow.services.database.models.api_key.crud import check_key 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.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.database.models.user.user import User
from langflow.services.deps import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
from sqlmodel import Session
oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login", auto_error=False) 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) fernet = get_fernet(settings_service)
# Two-way decryption # Two-way decryption
if isinstance(encrypted_api_key, str): if isinstance(encrypted_api_key, str):
encrypted_api_key = encrypted_api_key.encode() encoded_bytes = encrypted_api_key.encode()
decrypted_key = fernet.decrypt(encrypted_api_key).decode() else:
encoded_bytes = encrypted_api_key
decrypted_key = fernet.decrypt(encoded_bytes).decode()
return decrypted_key return decrypted_key

View file

@ -1,4 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from langflow.interface.run import build_sorted_vertices from langflow.interface.run import build_sorted_vertices
from langflow.services.base import Service from langflow.services.base import Service
from langflow.services.cache.utils import compute_dict_hash from langflow.services.cache.utils import compute_dict_hash
@ -14,7 +15,7 @@ class SessionService(Service):
def __init__(self, cache_service): def __init__(self, cache_service):
self.cache_service: "BaseCacheService" = 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 # Check if the data is cached
if key in self.cache_service: if key in self.cache_service:
return self.cache_service.get(key) return self.cache_service.get(key)
@ -23,7 +24,7 @@ class SessionService(Service):
key = self.generate_key(session_id=None, data_graph=data_graph) key = self.generate_key(session_id=None, data_graph=data_graph)
# If not cached, build the graph and cache it # 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)) self.cache_service.set(key, (graph, artifacts))

View file

@ -17,7 +17,7 @@ class ForbiddenError(CustomException):
class APIKeyError(CustomException): class APIKeyError(CustomException):
def __init__(self, detail="API key error"): def __init__(self, detail="API key error"):
super().__init__(detail, 401) super().__init__(detail, 400) #! Should be 401
class FilterError(CustomException): class FilterError(CustomException):

View file

@ -445,6 +445,8 @@ class StoreService(Service):
result: List[ListComponentResponse] = [] result: List[ListComponentResponse] = []
authorized = False authorized = False
metadata = {}
comp_count = 0
try: try:
result, metadata = await self.query_components( result, metadata = await self.query_components(
api_key=store_api_key, api_key=store_api_key,

View file

@ -1,3 +1,5 @@
from typing import Any, Dict, List
OPENAI_MODELS = [ OPENAI_MODELS = [
"text-davinci-003", "text-davinci-003",
"text-davinci-002", "text-davinci-002",
@ -59,3 +61,117 @@ DIRECT_TYPES = [
"code", "code",
"NestedDict", "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"],
},
]

View file

@ -145,16 +145,46 @@ def create_function(code, function_name):
def create_class(code, class_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"): if not hasattr(ast, "TypeIgnore"):
ast.TypeIgnore = create_type_ignore_class()
class TypeIgnore(ast.AST):
_fields = ()
ast.TypeIgnore = TypeIgnore
module = ast.parse(code) 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: for node in module.body:
if isinstance(node, ast.Import): if isinstance(node, ast.Import):
for alias in node.names: for alias in node.names:
@ -169,17 +199,47 @@ def create_class(code, class_name):
exec_globals[alias.name] = getattr(imported_module, alias.name) exec_globals[alias.name] = getattr(imported_module, alias.name)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"Module {node.module} not found. Please install it and try again.") from 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 = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name)
class_code.parent = None 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") code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec")
# This suppresses import errors return code_obj
# with contextlib.suppress(Exception):
exec(code_obj, exec_globals, locals())
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] exec_globals[class_name] = locals()[class_name]
# Return a function that imports necessary modules and creates an instance of the target class # 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(): for module_name, module in exec_globals.items():
if isinstance(module, type(importlib)): if isinstance(module, type(importlib)):
globals()[module_name] = module globals()[module_name] = module
@ -187,9 +247,13 @@ def create_class(code, class_name):
instance = exec_globals[class_name](*args, **kwargs) instance = exec_globals[class_name](*args, **kwargs)
return instance 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): def extract_function_name(code):

View file

@ -1,13 +1,9 @@
from typing import TYPE_CHECKING, Any, Dict, Optional from typing import TYPE_CHECKING, Any, Dict, Optional
from asgiref.sync import async_to_sync
from celery.exceptions import SoftTimeLimitExceeded # type: ignore from celery.exceptions import SoftTimeLimitExceeded # type: ignore
from langflow.core.celery_app import celery_app from langflow.core.celery_app import celery_app
from langflow.processing.process import ( from langflow.processing.process import Result, generate_result, process_inputs
Result,
generate_result,
process_inputs,
)
from langflow.services.deps import get_session_service from langflow.services.deps import get_session_service
from langflow.services.manager import initialize_session_service from langflow.services.manager import initialize_session_service
@ -27,7 +23,7 @@ def build_vertex(self, vertex: "Vertex") -> "Vertex":
""" """
try: try:
vertex.task_id = self.request.id vertex.task_id = self.request.id
vertex.build() async_to_sync(vertex.build)()
return vertex return vertex
except SoftTimeLimitExceeded as e: except SoftTimeLimitExceeded as e:
raise self.retry(exc=SoftTimeLimitExceeded("Task took too long"), countdown=2) from 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: if session_id is None:
session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph) session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph)
# Load the graph using SessionService # Load the graph using SessionService
graph, artifacts = session_service.load_session(session_id, data_graph) graph, artifacts = async_to_sync(session_service.load_session)(session_id, data_graph)
built_object = graph.build() built_object = graph.build()
processed_inputs = process_inputs(inputs, artifacts) processed_inputs = process_inputs(inputs, artifacts)
result = generate_result(built_object, processed_inputs) result = generate_result(built_object, processed_inputs)

View file

@ -15,6 +15,7 @@ import { FlowType } from "../../types/flow";
import { removeApiKeys } from "../../utils/reactflowUtils"; import { removeApiKeys } from "../../utils/reactflowUtils";
import { getTagsIds } from "../../utils/storeUtils"; import { getTagsIds } from "../../utils/storeUtils";
import BaseModal from "../baseModal"; import BaseModal from "../baseModal";
import { StoreContext } from "../../contexts/storeContext";
export default function ShareModal({ export default function ShareModal({
component, component,
@ -22,14 +23,17 @@ export default function ShareModal({
children, children,
open, open,
setOpen, setOpen,
disabled
}: { }: {
children?: ReactNode; children?: ReactNode;
is_component: boolean; is_component: boolean;
component: FlowType; component: FlowType;
open?: boolean; open?: boolean;
setOpen?: (open: boolean) => void; setOpen?: (open: boolean) => void;
disabled?: boolean;
}): JSX.Element { }): JSX.Element {
const { version, addFlow } = useContext(FlowsContext); const { version, addFlow } = useContext(FlowsContext);
const {hasApiKey} = useContext(StoreContext)
const { setSuccessData, setErrorData } = useContext(alertContext); const { setSuccessData, setErrorData } = useContext(alertContext);
const [checked, setChecked] = useState(true); const [checked, setChecked] = useState(true);
const [name, setName] = useState(component?.name ?? ""); const [name, setName] = useState(component?.name ?? "");
@ -46,10 +50,12 @@ export default function ShareModal({
useEffect(() => { useEffect(() => {
if (open || internalOpen) { if (open || internalOpen) {
handleGetTags(); if(hasApiKey){
handleGetNames(); handleGetTags();
handleGetNames();
}
} }
}, [open, internalOpen]); }, [open, internalOpen,hasApiKey]);
function handleGetTags() { function handleGetTags() {
setLoadingTags(true); setLoadingTags(true);
@ -113,7 +119,7 @@ export default function ShareModal({
return ( return (
<BaseModal <BaseModal
size="smaller-h-full" size="smaller-h-full"
open={open ?? internalOpen} open={(!disabled && open) ?? internalOpen}
setOpen={setOpen ?? internalSetOpen} setOpen={setOpen ?? internalSetOpen}
> >
<BaseModal.Trigger>{children ? children : <></>}</BaseModal.Trigger> <BaseModal.Trigger>{children ? children : <></>}</BaseModal.Trigger>

View file

@ -322,9 +322,9 @@ export default function Page({
); );
// Calculate the position where the node should be created // Calculate the position where the node should be created
const position = reactFlowInstance!.project({ const position = reactFlowInstance!.screenToFlowPosition({
x: event.clientX - reactflowBounds!.left, x: event.clientX,
y: event.clientY - reactflowBounds!.top, y: event.clientY
}); });
// Generate a unique node ID // Generate a unique node ID

View file

@ -23,12 +23,14 @@ import {
} from "../../../../utils/utils"; } from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent"; import DisclosureComponent from "../DisclosureComponent";
import SidebarDraggableComponent from "./sideBarDraggableComponent"; import SidebarDraggableComponent from "./sideBarDraggableComponent";
import { StoreContext } from "../../../../contexts/storeContext";
export default function ExtraSidebar(): JSX.Element { export default function ExtraSidebar(): JSX.Element {
const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } = const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } =
useContext(typesContext); useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } = const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
useContext(FlowsContext); useContext(FlowsContext);
const {hasApiKey} = useContext(StoreContext)
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const [dataFilter, setFilterData] = useState(data); const [dataFilter, setFilterData] = useState(data);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
@ -48,6 +50,8 @@ export default function ExtraSidebar(): JSX.Element {
event.dataTransfer.setData("nodedata", JSON.stringify(data)); event.dataTransfer.setData("nodedata", JSON.stringify(data));
} }
// Handle showing components after use search input // Handle showing components after use search input
function handleSearchInput(e: string) { function handleSearchInput(e: string) {
if (e === "") { if (e === "") {
@ -180,7 +184,7 @@ export default function ExtraSidebar(): JSX.Element {
const ModalMemo = useMemo( const ModalMemo = useMemo(
() => ( () => (
<ShareModal is_component={false} component={flow!}> <ShareModal is_component={false} component={flow!} disabled={!hasApiKey}>
<ShadTooltip content="Share" side="top"> <ShadTooltip content="Share" side="top">
<div className={classNames("extra-side-bar-buttons")}> <div className={classNames("extra-side-bar-buttons")}>
<IconComponent name="Share2" className="side-bar-button-size" /> <IconComponent name="Share2" className="side-bar-button-size" />
@ -188,16 +192,16 @@ export default function ExtraSidebar(): JSX.Element {
</ShadTooltip> </ShadTooltip>
</ShareModal> </ShareModal>
), ),
[] [hasApiKey]
); );
const ExportMemo = useMemo( const ExportMemo = useMemo(
() => ( () => (
<ExportModal> <ExportModal>
<ShadTooltip content="Export" side="top"> <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" /> <IconComponent name="FileDown" className="side-bar-button-size" />
</div> </button>
</ShadTooltip> </ShadTooltip>
</ExportModal> </ExportModal>
), ),
@ -305,7 +309,15 @@ export default function ExtraSidebar(): JSX.Element {
<div className="side-bar-components-div-arrangement"> <div className="side-bar-components-div-arrangement">
{Object.keys(dataFilter) {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) => .map((SBSectionName: keyof APIObjectType, index) =>
Object.keys(dataFilter[SBSectionName]).length > 0 ? ( Object.keys(dataFilter[SBSectionName]).length > 0 ? (
<DisclosureComponent <DisclosureComponent

View file

@ -93,11 +93,11 @@ export default function SidebarDraggableComponent({
> >
<span className="side-bar-components-text">{display_name}</span> <span className="side-bar-components-text">{display_name}</span>
<div> <div>
<SelectTrigger> <IconComponent
<IconComponent
name="Menu" name="Menu"
className="side-bar-components-icon " className="side-bar-components-icon "
/> />
<SelectTrigger>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value={"download"}> <SelectItem value={"download"}>

View file

@ -21,6 +21,7 @@ import {
updateFlowPosition, updateFlowPosition,
} from "../../../../utils/reactflowUtils"; } from "../../../../utils/reactflowUtils";
import { classNames } from "../../../../utils/utils"; import { classNames } from "../../../../utils/utils";
import { StoreContext } from "../../../../contexts/storeContext";
export default function NodeToolbarComponent({ export default function NodeToolbarComponent({
data, data,
@ -50,6 +51,7 @@ export default function NodeToolbarComponent({
); );
const updateNodeInternals = useUpdateNodeInternals(); const updateNodeInternals = useUpdateNodeInternals();
const { getNodeId } = useContext(FlowsContext); const { getNodeId } = useContext(FlowsContext);
const {hasApiKey} = useContext(StoreContext)
function canMinimize() { function canMinimize() {
let countHandles: number = 0; let countHandles: number = 0;
@ -87,7 +89,7 @@ export default function NodeToolbarComponent({
downloadNode(createFlowComponent(cloneDeep(data), version)); downloadNode(createFlowComponent(cloneDeep(data), version));
break; break;
case "Share": case "Share":
setShowconfirmShare(true); if(hasApiKey) setShowconfirmShare(true);
break; break;
case "SaveAll": case "SaveAll":
saveComponent(cloneDeep(data)); saveComponent(cloneDeep(data));
@ -211,7 +213,7 @@ export default function NodeToolbarComponent({
Save{" "} Save{" "}
</div>{" "} </div>{" "}
</SelectItem> </SelectItem>
<SelectItem value={"Share"}> {hasApiKey && <SelectItem value={"Share"}>
<div className="flex"> <div className="flex">
<IconComponent <IconComponent
name="Share2" name="Share2"
@ -219,7 +221,7 @@ export default function NodeToolbarComponent({
/>{" "} />{" "}
Share{" "} Share{" "}
</div>{" "} </div>{" "}
</SelectItem> </SelectItem>}
<SelectItem value={"Download"}> <SelectItem value={"Download"}>
<div className="flex"> <div className="flex">
<IconComponent <IconComponent

View file

@ -1,18 +1,17 @@
import ast import ast
import pytest
import types import types
from uuid import uuid4 from uuid import uuid4
import pytest
from fastapi import HTTPException 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.base import CustomComponent
from langflow.interface.custom.code_parser import CodeParser, CodeSyntaxError
from langflow.interface.custom.component import ( from langflow.interface.custom.component import (
Component, Component,
ComponentCodeNullError, ComponentCodeNullError,
) )
from langflow.interface.custom.code_parser import CodeParser, CodeSyntaxError from langflow.services.database.models.flow import Flow, FlowCreate
code_default = """ code_default = """
from langflow import Prompt from langflow import Prompt
@ -229,9 +228,11 @@ def test_custom_component_get_function_entrypoint_return_type():
Test the get_function_entrypoint_return_type Test the get_function_entrypoint_return_type
property of the CustomComponent class. property of the CustomComponent class.
""" """
from langchain.schema import Document
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type 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(): 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") custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type 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(): def test_custom_component_get_main_class_name_no_main_class():

View file

@ -1,32 +1,30 @@
import copy import copy
import json import json
import os import os
from pathlib import Path
import pickle import pickle
from pathlib import Path
from typing import Type, Union 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 import pytest
from langchain.agents import AgentExecutor
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.llms.fake import FakeListLLM from langchain.llms.fake import FakeListLLM
from langflow.graph import Graph from langflow.graph import Graph
from langflow.graph.vertex.types import ( from langflow.graph.edge.base import Edge
FileToolVertex,
LLMVertex,
ToolkitVertex,
)
from langflow.processing.process import get_result_and_thought
from langflow.utils.payload import get_root_node
from langflow.graph.graph.utils import ( from langflow.graph.graph.utils import (
find_last_node, find_last_node,
process_flow,
set_new_target_handle, set_new_target_handle,
ungroup_node, ungroup_node,
process_flow,
update_source_handle, update_source_handle,
update_target_handle, update_target_handle,
update_template, 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 # Test cases for the graph module
@ -232,29 +230,32 @@ def test_build_params(basic_graph):
assert "memory" in root.params assert "memory" in root.params
def test_build(basic_graph): @pytest.mark.asyncio
async def test_build(basic_graph):
"""Test Node's build method""" """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 that the agent was built"""
assert isinstance(graph, Graph) assert isinstance(graph, Graph)
# Now we test the build method # Now we test the build method
# Build the Agent # Build the Agent
result = graph.build() result = await graph.build()
# The agent should be a AgentExecutor # The agent should be a AgentExecutor
assert isinstance(result, Chain) 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) llm_node = get_node_by_type(basic_graph, LLMVertex)
assert llm_node is not None assert llm_node is not None
built_object = llm_node.build() built_object = await llm_node.build()
assert built_object is not None 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 # Write a file to the disk
file_path = "api-with-examples.yaml" file_path = "api-with-examples.yaml"
with open(file_path, "w") as f: 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) toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
assert toolkit_node is not None assert toolkit_node is not None
built_object = toolkit_node.build() built_object = await toolkit_node.build()
assert built_object is not None assert built_object is not UnbuiltObject
# Remove the file # Remove the file
os.remove(file_path) os.remove(file_path)
assert not Path(file_path).exists() 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" file_path = "api-with-examples.yaml"
with open(file_path, "w") as f: with open(file_path, "w") as f:
f.write("openapi: 3.0.0") f.write("openapi: 3.0.0")
assert Path(file_path).exists() assert Path(file_path).exists()
file_tool_node = get_node_by_type(openapi_graph, FileToolVertex) file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
assert file_tool_node is not None assert file_tool_node is not UnbuiltObject
built_object = file_tool_node.build() built_object = await file_tool_node.build()
assert built_object is not None assert built_object is not UnbuiltObject
# Remove the file # Remove the file
os.remove(file_path) os.remove(file_path)
assert not Path(file_path).exists() assert not Path(file_path).exists()
# def test_wrapper_node_build(openapi_graph): @pytest.mark.asyncio
# wrapper_node = get_node_by_type(openapi_graph, WrapperVertex) async def test_get_result_and_thought(basic_graph):
# 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):
"""Test the get_result_and_thought method""" """Test the get_result_and_thought method"""
responses = [ responses = [
"Final Answer: I am a response", "Final Answer: I am a response",
@ -303,7 +299,7 @@ def test_get_result_and_thought(basic_graph):
assert llm_node is not None assert llm_node is not None
llm_node._built_object = FakeListLLM(responses=responses) llm_node._built_object = FakeListLLM(responses=responses)
llm_node._built = True llm_node._built = True
langchain_object = basic_graph.build() langchain_object = await basic_graph.build()
# assert all nodes are built # assert all nodes are built
assert all(node._built for node in basic_graph.nodes) assert all(node._built for node in basic_graph.nodes)
# now build again and check if FakeListLLM was used # 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" 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) loaded_json = json.loads(json_vector_store)
graph = Graph.from_payload(loaded_json) graph = Graph.from_payload(loaded_json)
assert isinstance(graph, Graph) assert isinstance(graph, Graph)
first_result = graph.build() first_result = await graph.build()
assert isinstance(first_result, AgentExecutor) assert isinstance(first_result, AgentExecutor)
pickled = pickle.dumps(graph) pickled = pickle.dumps(graph)
assert pickled is not None assert pickled is not UnbuiltObject
unpickled = pickle.loads(pickled) unpickled = pickle.loads(pickled)
assert unpickled is not None assert unpickled is not UnbuiltObject
result = unpickled.build() result = await unpickled.build()
assert isinstance(result, AgentExecutor) 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) loaded_json = json.loads(json_vector_store)
graph = Graph.from_payload(loaded_json) graph = Graph.from_payload(loaded_json)
assert isinstance(graph, Graph) assert isinstance(graph, Graph)
for vertex in graph.nodes: for vertex in graph.nodes:
vertex.build() await vertex.build()
pickled = pickle.dumps(vertex) pickled = pickle.dumps(vertex)
assert pickled is not None assert pickled is not UnbuiltObject
unpickled = pickle.loads(pickled) unpickled = pickle.loads(pickled)
assert unpickled is not None assert unpickled is not UnbuiltObject

View file

@ -1,3 +1,4 @@
import pytest
from langflow.processing.process import process_tweaks from langflow.processing.process import process_tweaks
from langflow.services.deps import get_session_service from langflow.services.deps import get_session_service
@ -197,39 +198,42 @@ def test_tweak_not_in_template():
assert result == graph_data 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 # Provide a non-existent session_id
session_service = get_session_service() session_service = get_session_service()
session_id1 = "non-existent-session-id" 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 # 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 graph1 == graph2
assert artifacts1 == artifacts2 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 # Provide a non-existent session_id
session_service = get_session_service() session_service = get_session_service()
session_id1 = "non-existent-session-id" session_id1 = "non-existent-session-id"
session_id = session_service.build_key(session_id1, basic_graph_data) 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 # Clear the cache
session_service.clear_session(session_id) session_service.clear_session(session_id)
# Use the new session_id to get the langchain_object again # 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) assert id(graph1) != id(graph2)
# Since the cache was cleared, objects should be different # 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 # Provide a non-existent session_id
session_service = get_session_service() session_service = get_session_service()
session_id1 = None 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 # 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 graph1 == graph2