Merge branch 'logspace-ai:dev' into dev

This commit is contained in:
Dave Morris 2023-08-06 16:02:03 -05:00 • committed by GitHub
commit 4984a1282d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 485 additions and 298 deletions

View file

@ -14,9 +14,7 @@ env:
jobs: jobs:
if_release: if_release:
if: | if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'Release') }}
${{ github.event.pull_request.merged == true }}
&& ${{ contains(github.event.pull_request.labels.*.name, 'Release') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View file

@ -33,7 +33,7 @@
- [HuggingFace Spaces](#huggingface-spaces) - [HuggingFace Spaces](#huggingface-spaces)
- [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli) - [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli)
- [Usage](#usage) - [Usage](#usage)
- [Environment Variables](#environment-variables) - [Environment Variables](#environment-variables)
- [Deployment](#deployment) - [Deployment](#deployment)
- [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform) - [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform)
- [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud) - [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud)
@ -112,7 +112,6 @@ Each option is detailed below:
- `--cache`: Selects the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`. - `--cache`: Selects the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`.
- `--jcloud/--no-jcloud`: Toggles the option to deploy on Jina AI Cloud. The default is `no-jcloud`. - `--jcloud/--no-jcloud`: Toggles the option to deploy on Jina AI Cloud. The default is `no-jcloud`.
- `--dev/--no-dev`: Toggles the development mode. The default is `no-dev`. - `--dev/--no-dev`: Toggles the development mode. The default is `no-dev`.
- `--database-url`: Sets the database URL to connect to. If not provided, a local SQLite database will be used. Can be set using the `LANGFLOW_DATABASE_URL` environment variable.
- `--path`: Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable. - `--path`: Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable.
- `--open-browser/--no-open-browser`: Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`. - `--open-browser/--no-open-browser`: Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`.
- `--remove-api-keys/--no-remove-api-keys`: Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`. - `--remove-api-keys/--no-remove-api-keys`: Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`.

View file

@ -15,4 +15,4 @@ COPY ./ ./
# Install dependencies # Install dependencies
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
CMD ["uvicorn", "langflow.main:app", "--host", "0.0.0.0", "--port", "5003", "--reload", "log-level", "debug"] CMD ["uvicorn","--factory", "langflow.main:create_app", "--host", "0.0.0.0", "--port", "5003", "--reload", "log-level", "debug"]

View file

@ -1,4 +1,4 @@
version: '3.4' version: "3.4"
services: services:
backend: backend:
@ -7,7 +7,12 @@ services:
build: build:
context: ./ context: ./
dockerfile: ./dev.Dockerfile dockerfile: ./dev.Dockerfile
command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn langflow.main:app --host 0.0.0.0 --port 7860 --reload"] command:
[
"sh",
"-c",
"pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload",
]
ports: ports:
- 7860:7860 - 7860:7860
- 5678:5678 - 5678:5678
@ -22,7 +27,7 @@ services:
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
- ./src/frontend/public:/home/node/app/public - ./src/frontend/public:/home/node/app/public
- ./src/frontend/src:/home/node/app/src - ./src/frontend/src:/home/node/app/src
- ./src/frontend/package.json:/home/node/app/package.json - ./src/frontend/package.json:/home/node/app/package.json
restart: on-failure restart: on-failure

View file

@ -1,4 +1,4 @@
version: '3' version: "3"
services: services:
backend: backend:
@ -9,7 +9,7 @@ services:
- "7860:7860" - "7860:7860"
volumes: volumes:
- ./:/app - ./:/app
command: bash -c "uvicorn langflow.main:app --host 0.0.0.0 --port 7860 --reload" command: bash -c "uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload"
frontend: frontend:
build: build:
@ -22,7 +22,7 @@ services:
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
- ./src/frontend/public:/home/node/app/public - ./src/frontend/public:/home/node/app/public
- ./src/frontend/src:/home/node/app/src - ./src/frontend/src:/home/node/app/src
- ./src/frontend/package.json:/home/node/app/package.json - ./src/frontend/package.json:/home/node/app/package.json
restart: on-failure restart: on-failure

View file

@ -385,19 +385,19 @@ Your structure should look something like this:
### Loading Custom Components ### Loading Custom Components
You can specify the path to your custom components using the _`--components-path`_ argument when running the Langflow CLI, as shown below: The recommended way to load custom components is to set the _`LANGFLOW_COMPONENTS_PATH`_ environment variable to the path of your custom components directory. Then, run the Langflow CLI as usual.
```bash
langflow --components-path /path/to/components
```
Alternatively, you can set the `LANGFLOW_COMPONENTS_PATH` environment variable:
```bash ```bash
export LANGFLOW_COMPONENTS_PATH=/path/to/components export LANGFLOW_COMPONENTS_PATH=/path/to/components
langflow langflow
``` ```
Alternatively, you can specify the path to your custom components using the _`--components-path`_ argument when running the Langflow CLI, as shown below:
```bash
langflow --components-path /path/to/components
```
Langflow will attempt to load all of the components found in the specified directory. If a component fails to load due to errors in the component's code, Langflow will print an error message to the console but will continue loading the rest of the components. Langflow will attempt to load all of the components found in the specified directory. If a component fails to load due to errors in the component's code, Langflow will print an error message to the console but will continue loading the rest of the components.
### Interacting with Custom Components ### Interacting with Custom Components

76
poetry.lock generated
View file

@ -146,13 +146,13 @@ files = [
[[package]] [[package]]
name = "anthropic" name = "anthropic"
version = "0.3.7" version = "0.3.8"
description = "Client library for the anthropic API" description = "Client library for the anthropic API"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
{file = "anthropic-0.3.7-py3-none-any.whl", hash = "sha256:ca57635d7f13d609aa8a5b93a834e067760d96b9657bdf81e0c7444ddf41fc64"}, {file = "anthropic-0.3.8-py3-none-any.whl", hash = "sha256:97ffe1bacc4214dc89b19f496cf2769746971e86f7c835a05aa21b76f260d279"},
{file = "anthropic-0.3.7.tar.gz", hash = "sha256:0453f80ba8224364c8b0dae0b5088becd67277de57708d7b887ebb6c2ceb3c49"}, {file = "anthropic-0.3.8.tar.gz", hash = "sha256:6651099807456c3b95b3879f5ad7d00f7e7e4f7649a2394d18032ab8be54ef16"},
] ]
[package.dependencies] [package.dependencies]
@ -739,18 +739,19 @@ sqlalchemy = ["sqlalchemy (>1.3.21,<2.0)"]
[[package]] [[package]]
name = "cohere" name = "cohere"
version = "4.17.0" version = "4.19.2"
description = "" description = ""
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
{file = "cohere-4.17.0-py3-none-any.whl", hash = "sha256:44e0bdb0a2d9467506d27b285f542177b98f92647f27e17ea921a01006fe2f33"}, {file = "cohere-4.19.2-py3-none-any.whl", hash = "sha256:0b6a4fe04380a481a8e975ebcc9bb6433febe4d3eb583b6d6e04342a5e998345"},
{file = "cohere-4.17.0.tar.gz", hash = "sha256:9f479543b50490b4cb6385468d7571ad891a09cde7bd6b028171596bac6ce6ff"}, {file = "cohere-4.19.2.tar.gz", hash = "sha256:a0b0fa698b3d3983fb328bb90d68fcf08faaa2268f3772ebc6bfea6ba55acf27"},
] ]
[package.dependencies] [package.dependencies]
aiohttp = ">=3.0,<4.0" aiohttp = ">=3.0,<4.0"
backoff = ">=2.0,<3.0" backoff = ">=2.0,<3.0"
fastavro = {version = "1.8.2", markers = "python_version >= \"3.8\""}
importlib_metadata = ">=6.0,<7.0" importlib_metadata = ">=6.0,<7.0"
requests = ">=2.25.0,<3.0.0" requests = ">=2.25.0,<3.0.0"
urllib3 = ">=1.26,<3" urllib3 = ">=1.26,<3"
@ -924,13 +925,13 @@ test-randomorder = ["pytest-randomly"]
[[package]] [[package]]
name = "ctransformers" name = "ctransformers"
version = "0.2.17" version = "0.2.18"
description = "Python bindings for the Transformer models implemented in C/C++ using GGML library." description = "Python bindings for the Transformer models implemented in C/C++ using GGML library."
optional = true optional = true
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "ctransformers-0.2.17-py3-none-any.whl", hash = "sha256:903c16b38f5b2750ee34b90107c3a72351d7a9a201a6987a6560bd50874e9698"}, {file = "ctransformers-0.2.18-py3-none-any.whl", hash = "sha256:6d693e269d797f2485c86c9e8a322acb1464e782bf22547ec57e09d4ccbb4523"},
{file = "ctransformers-0.2.17.tar.gz", hash = "sha256:0c9de34cc8295ba6cb940e413130e6658fac54a99cecfa6098ac04638fd9301e"}, {file = "ctransformers-0.2.18.tar.gz", hash = "sha256:fc09c399baf16d99dd44aa6671d49aa4060d8904324c2527aa58e8831476c370"},
] ]
[package.dependencies] [package.dependencies]
@ -938,6 +939,7 @@ huggingface-hub = "*"
py-cpuinfo = ">=9.0.0,<10.0.0" py-cpuinfo = ">=9.0.0,<10.0.0"
[package.extras] [package.extras]
gptq = ["exllama (==0.1.0)"]
tests = ["pytest"] tests = ["pytest"]
[[package]] [[package]]
@ -1641,13 +1643,13 @@ six = "*"
[[package]] [[package]]
name = "google-cloud-aiplatform" name = "google-cloud-aiplatform"
version = "1.28.1" version = "1.29.0"
description = "Vertex AI API client library" description = "Vertex AI API client library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "google-cloud-aiplatform-1.28.1.tar.gz", hash = "sha256:b6468db7dc50295c988edf6505f0bf4d4bb2321de28873b2a4a87fd384be6308"}, {file = "google-cloud-aiplatform-1.29.0.tar.gz", hash = "sha256:fceabb924d2d26057e3c8c5c2e251929389aa6d553361377bc402781150c0db3"},
{file = "google_cloud_aiplatform-1.28.1-py2.py3-none-any.whl", hash = "sha256:5587b8d4599047117b0c787635a00e8e3893b75944993e2faf784176442e9de4"}, {file = "google_cloud_aiplatform-1.29.0-py2.py3-none-any.whl", hash = "sha256:cf81c1d93c61ccf3df60a65e3a5a1e465e044059d36b6fc1202b940c46c4c1e1"},
] ]
[package.dependencies] [package.dependencies]
@ -1728,13 +1730,13 @@ grpc = ["grpcio (>=1.38.0,<2.0dev)"]
[[package]] [[package]]
name = "google-cloud-resource-manager" name = "google-cloud-resource-manager"
version = "1.10.2" version = "1.10.3"
description = "Google Cloud Resource Manager API client library" description = "Google Cloud Resource Manager API client library"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "google-cloud-resource-manager-1.10.2.tar.gz", hash = "sha256:9a7bdd0347ad553376cc66ad317c5223d1ae04bdcf74edcbfcd12605cff7b510"}, {file = "google-cloud-resource-manager-1.10.3.tar.gz", hash = "sha256:f80efcea36f10c5a81889afe93910926e3978b4b1ceeb82f563a2fc863072d14"},
{file = "google_cloud_resource_manager-1.10.2-py2.py3-none-any.whl", hash = "sha256:9e074c28326bd1632f1a270c20cfea1ffe98f49cf821033e65bdac55661ffbd5"}, {file = "google_cloud_resource_manager-1.10.3-py2.py3-none-any.whl", hash = "sha256:1381a4b0f522248ebe0ebd1289d8822b99c54f4e1fe03924a6e723b2ed93dd7f"},
] ]
[package.dependencies] [package.dependencies]
@ -2561,12 +2563,12 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)",
[[package]] [[package]]
name = "jcloud" name = "jcloud"
version = "0.2.14" version = "0.2.16"
description = "Simplify deploying and managing Jina projects on Jina Cloud" description = "Simplify deploying and managing Jina projects on Jina Cloud"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
{file = "jcloud-0.2.14.tar.gz", hash = "sha256:60798a41be370cacf380bb024f04086df8b0e65f20e51b4fbe1dd11e04d95be7"}, {file = "jcloud-0.2.16.tar.gz", hash = "sha256:abf39a70fc5852574a05e03dac5e5cc364df87ae1f8476cbd441178f14adb578"},
] ]
[package.dependencies] [package.dependencies]
@ -2890,6 +2892,20 @@ openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.4.0)"]
qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
text-helpers = ["chardet (>=5.1.0,<6.0.0)"] text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
[[package]]
name = "langchain-experimental"
version = "0.0.8"
description = "Building applications with LLMs through composability"
optional = false
python-versions = ">=3.8.1,<4.0"
files = [
{file = "langchain_experimental-0.0.8-py3-none-any.whl", hash = "sha256:34cf202ba29fdef178c5d68772cf2fa08dd2a0fad588ca3ef324ae71e596cc21"},
{file = "langchain_experimental-0.0.8.tar.gz", hash = "sha256:35d198f8e70a053ccd84273198ff08b4b700ac03ec5f43ba9b7ef797dd42ad14"},
]
[package.dependencies]
langchain = ">=0.0.239"
[[package]] [[package]]
name = "langchain-serve" name = "langchain-serve"
version = "0.0.58" version = "0.0.58"
@ -2917,13 +2933,13 @@ test = ["psutil", "pytest", "pytest-asyncio"]
[[package]] [[package]]
name = "langsmith" name = "langsmith"
version = "0.0.16" version = "0.0.18"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false optional = false
python-versions = ">=3.8.1,<4.0" python-versions = ">=3.8.1,<4.0"
files = [ files = [
{file = "langsmith-0.0.16-py3-none-any.whl", hash = "sha256:a9a5b30dc1c0ee0c9f8bbd2076856f1db3856a9521464f130be1db519c7077e5"}, {file = "langsmith-0.0.18-py3-none-any.whl", hash = "sha256:58f3eb029495e3696c3c94c95adfb2dd15043d82928ef932a7829fe020ad5522"},
{file = "langsmith-0.0.16.tar.gz", hash = "sha256:0729df30dd5b1c9d0c83614b5d348c7358ac182d7e3003dafbfcbedb88cbc8b8"}, {file = "langsmith-0.0.18.tar.gz", hash = "sha256:d8d97542f99352c5470bff74a4fbc9bd322dea850217a8ab71badcefa041566b"},
] ]
[package.dependencies] [package.dependencies]
@ -3311,13 +3327,13 @@ files = [
[[package]] [[package]]
name = "more-itertools" name = "more-itertools"
version = "10.0.0" version = "10.1.0"
description = "More routines for operating on iterables, beyond itertools" description = "More routines for operating on iterables, beyond itertools"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "more-itertools-10.0.0.tar.gz", hash = "sha256:cd65437d7c4b615ab81c0640c0480bc29a550ea032891977681efd28344d51e1"}, {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"},
{file = "more_itertools-10.0.0-py3-none-any.whl", hash = "sha256:928d514ffd22b5b0a8fce326d57f423a55d2ff783b093bab217eda71e732330f"}, {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"},
] ]
[[package]] [[package]]
@ -5477,13 +5493,13 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""}
[[package]] [[package]]
name = "qdrant-client" name = "qdrant-client"
version = "1.3.2" version = "1.4.0"
description = "Client library for the Qdrant vector search engine" description = "Client library for the Qdrant vector search engine"
optional = false optional = false
python-versions = ">=3.7,<3.12" python-versions = ">=3.7,<3.12"
files = [ files = [
{file = "qdrant_client-1.3.2-py3-none-any.whl", hash = "sha256:66a076016fb9d33bec8170e96516d7e4a0ee5c611824cc9be18590ffeb3cf9aa"}, {file = "qdrant_client-1.4.0-py3-none-any.whl", hash = "sha256:2f9e563955b5163da98016f2ed38d9aea5058576c7c5844e9aa205d28155f56d"},
{file = "qdrant_client-1.3.2.tar.gz", hash = "sha256:6638c9eac027f2c0fdb1f63c3bd7b403fe8c3f73cb1f21fd15fd60f71012d537"}, {file = "qdrant_client-1.4.0.tar.gz", hash = "sha256:2e54f5a80eb1e7e67f4603b76365af4817af15fb3d0c0f44de4fd93afbbe5537"},
] ]
[package.dependencies] [package.dependencies]
@ -6380,13 +6396,13 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"]
[[package]] [[package]]
name = "textual" name = "textual"
version = "0.31.0" version = "0.32.0"
description = "Modern Text User Interface framework" description = "Modern Text User Interface framework"
optional = true optional = true
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
{file = "textual-0.31.0-py3-none-any.whl", hash = "sha256:1243bccadb28e1ff46bdfe676ee25a6ce52756842bc9dca4d824e0bc4d7d9a42"}, {file = "textual-0.32.0-py3-none-any.whl", hash = "sha256:81fc68406c8806bc864e2f035874a868b4ff0cf466289dce5f7b31869949383b"},
{file = "textual-0.31.0.tar.gz", hash = "sha256:e2b43f1c26b21731ee83f558f8d6cb4f7163e3a713854c36cd7785139a0e4e51"}, {file = "textual-0.32.0.tar.gz", hash = "sha256:f7b6683bc18faee6fd3c47cfbad43fbf8273c5fecc12230d52ce5ee089021327"},
] ]
[package.dependencies] [package.dependencies]
@ -7564,4 +7580,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 = "154238df4e8b249196011f592d63e2fad0e18316960e400037f54453c813d47a" content-hash = "57abce2ebcdc3cd7e359c36805822b9398d3bfb500c175b173a6d784d1276df6"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.4.0" version = "0.4.1"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -76,6 +76,7 @@ google-cloud-aiplatform = "^1.26.1"
psycopg = "^3.1.9" psycopg = "^3.1.9"
psycopg-binary = "^3.1.9" psycopg-binary = "^3.1.9"
fastavro = "^1.8.0" fastavro = "^1.8.0"
langchain-experimental = "^0.0.8"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"

View file

@ -1,4 +1,3 @@
import os
import sys import sys
import time import time
import httpx import httpx
@ -25,49 +24,25 @@ def update_settings(
config: str, config: str,
cache: str, cache: str,
dev: bool = False, dev: bool = False,
database_url: Optional[str] = None,
remove_api_keys: bool = False, remove_api_keys: bool = False,
components_path: Optional[Path] = None, components_path: Optional[Path] = None,
): ):
"""Update the settings from a config file.""" """Update the settings from a config file."""
# Check for database_url in the environment variables # Check for database_url in the environment variables
database_url = database_url or os.getenv("langflow_database_url")
if config: if config:
logger.debug(f"Loading settings from {config}") logger.debug(f"Loading settings from {config}")
settings.update_from_yaml(config, dev=dev) settings.update_from_yaml(config, dev=dev)
if database_url:
settings.update_settings(database_url=database_url)
if remove_api_keys: if remove_api_keys:
logger.debug(f"Setting remove_api_keys to {remove_api_keys}") logger.debug(f"Setting remove_api_keys to {remove_api_keys}")
settings.update_settings(remove_api_keys=remove_api_keys) settings.update_settings(REMOVE_API_KEYS=remove_api_keys)
if cache: if cache:
logger.debug(f"Setting cache to {cache}") logger.debug(f"Setting cache to {cache}")
settings.update_settings(cache=cache) settings.update_settings(CACHE=cache)
if components_path: if components_path:
logger.debug(f"Adding component path {components_path}") logger.debug(f"Adding component path {components_path}")
settings.update_settings(components_path=components_path) settings.update_settings(COMPONENTS_PATH=components_path)
def load_params():
"""
Load the parameters from the environment variables.
"""
global_vars = globals()
for key, value in global_vars.items():
env_key = f"LANGFLOW_{key.upper()}"
if env_key in os.environ:
if isinstance(value, bool):
# Handle booleans
global_vars[key] = os.getenv(env_key, str(value)).lower() == "true"
elif isinstance(value, int):
# Handle integers
global_vars[key] = int(os.getenv(env_key, str(value)))
elif isinstance(value, str) or value is None:
# Handle strings and None values
global_vars[key] = os.getenv(env_key, str(value))
def serve_on_jcloud(): def serve_on_jcloud():
@ -134,7 +109,7 @@ def serve(
config: str = typer.Option("config.yaml", help="Path to the configuration file."), config: str = typer.Option("config.yaml", help="Path to the configuration file."),
# .env file param # .env file param
env_file: Path = typer.Option( env_file: Path = typer.Option(
".env", help="Path to the .env file containing environment variables." None, help="Path to the .env file containing environment variables."
), ),
log_level: str = typer.Option( log_level: str = typer.Option(
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL" "critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
@ -149,11 +124,13 @@ def serve(
), ),
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"), jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"), dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
database_url: str = typer.Option( # This variable does not work but is set by the .env file
None, # and works with Pydantic
help="Database URL to connect to. If not provided, a local SQLite database will be used.", # database_url: str = typer.Option(
envvar="LANGFLOW_DATABASE_URL", # None,
), # help="Database URL to connect to. If not provided, a local SQLite database will be used.",
# envvar="LANGFLOW_DATABASE_URL",
# ),
path: str = typer.Option( path: str = typer.Option(
None, None,
help="Path to the frontend directory containing build files. This is for development purposes only.", help="Path to the frontend directory containing build files. This is for development purposes only.",
@ -176,7 +153,6 @@ def serve(
# override env variables with .env file # override env variables with .env file
if env_file: if env_file:
load_dotenv(env_file, override=True) load_dotenv(env_file, override=True)
load_params()
if jcloud: if jcloud:
return serve_on_jcloud() return serve_on_jcloud()
@ -185,7 +161,6 @@ def serve(
update_settings( update_settings(
config, config,
dev=dev, dev=dev,
database_url=database_url,
remove_api_keys=remove_api_keys, remove_api_keys=remove_api_keys,
cache=cache, cache=cache,
components_path=components_path, components_path=components_path,

View file

@ -125,9 +125,8 @@ async def stream_build(flow_id: str):
vertex.build() vertex.build()
params = vertex._built_object_repr() params = vertex._built_object_repr()
valid = True valid = True
logger.debug( logger.debug(f"Building node {str(vertex.vertex_type)}")
f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}" logger.debug(f"Output: {params}")
)
if vertex.artifacts: if vertex.artifacts:
# The artifacts will be prompt variables # The artifacts will be prompt variables
# passed to build_input_keys_response # passed to build_input_keys_response

View file

@ -11,9 +11,6 @@ from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import (
CustomComponentPathValueError,
)
from langflow.api.v1.schemas import ( from langflow.api.v1.schemas import (
ProcessResponse, ProcessResponse,
@ -38,49 +35,27 @@ router = APIRouter(tags=["Base"])
@router.get("/all") @router.get("/all")
def get_all(): def get_all():
logger.debug("Building langchain types dict")
native_components = build_langchain_types_dict() native_components = build_langchain_types_dict()
# custom_components is a list of dicts # custom_components is a list of dicts
# need to merge all the keys into one dict # need to merge all the keys into one dict
custom_components_from_file = {} custom_components_from_file = {}
if settings.components_path: if settings.COMPONENTS_PATH:
logger.info(f"Building custom components from {settings.COMPONENTS_PATH}")
custom_component_dicts = [ custom_component_dicts = [
build_langchain_custom_component_list_from_path(str(path)) build_langchain_custom_component_list_from_path(str(path))
for path in settings.components_path for path in settings.COMPONENTS_PATH
] ]
logger.info(f"Loading {len(custom_component_dicts)} custom components")
for custom_component_dict in custom_component_dicts: for custom_component_dict in custom_component_dicts:
custom_components_from_file = merge_nested_dicts( custom_components_from_file = merge_nested_dicts(
custom_components_from_file, custom_component_dict custom_components_from_file, custom_component_dict
) )
logger.info(f"Loaded {custom_component_dict}")
return merge_nested_dicts(native_components, custom_components_from_file) return merge_nested_dicts(native_components, custom_components_from_file)
@router.get("/load_custom_component_from_path")
def get_load_custom_component_from_path(path: str):
try:
data = build_langchain_custom_component_list_from_path(path)
except CustomComponentPathValueError as err:
raise HTTPException(
status_code=400,
detail={"error": type(err).__name__, "traceback": str(err)},
) from err
return data
@router.get("/load_custom_component_from_path_TEST")
def get_load_custom_component_from_path_test(path: str):
from langflow.interface.custom.directory_reader import (
DirectoryReader,
)
reader = DirectoryReader(path, False)
file_list = reader.get_files()
data = reader.build_component_menu_list(file_list)
return reader.filter_loaded_components(data, True)
# For backwards compatibility we will keep the old endpoint # For backwards compatibility we will keep the old endpoint
@router.post("/predict/{flow_id}", response_model=ProcessResponse) @router.post("/predict/{flow_id}", response_model=ProcessResponse)
@router.post("/process/{flow_id}", response_model=ProcessResponse) @router.post("/process/{flow_id}", response_model=ProcessResponse)

View file

@ -61,7 +61,7 @@ def update_flow(
if not db_flow: if not db_flow:
raise HTTPException(status_code=404, detail="Flow not found") raise HTTPException(status_code=404, detail="Flow not found")
flow_data = flow.dict(exclude_unset=True) flow_data = flow.dict(exclude_unset=True)
if settings.remove_api_keys: if settings.REMOVE_API_KEYS:
flow_data = remove_api_keys(flow_data) flow_data = remove_api_keys(flow_data)
for key, value in flow_data.items(): for key, value in flow_data.items():
setattr(db_flow, key, value) setattr(db_flow, key, value)

View file

@ -0,0 +1,4 @@
from langflow.interface.custom.custom_component import CustomComponent
__all__ = ["CustomComponent"]

View file

@ -0,0 +1,33 @@
from langflow import CustomComponent
from langchain.llms.base import BaseLLM
from langchain import PromptTemplate
from langchain.schema import Document
class PromptRunner(CustomComponent):
display_name: str = "Prompt Runner"
description: str = "Run a Chain with the given PromptTemplate"
beta = True
field_config = {
"llm": {"display_name": "LLM"},
"prompt": {
"display_name": "Prompt Template",
"info": "Make sure the prompt has all variables filled.",
},
"code": {"show": False},
"inputs": {"field_type": "code"},
}
def build(
self,
llm: BaseLLM,
prompt: PromptTemplate,
) -> Document:
chain = prompt | llm
# The input is an empty dict because the prompt is already filled
result = chain.invoke({})
if hasattr(result, "content"):
result = result.content
self.repr_value = result
return Document(page_content=str(result))

View file

@ -1,21 +1,48 @@
from contextlib import contextmanager from contextlib import contextmanager
from langflow.settings import settings import os
from sqlmodel import SQLModel, Session, create_engine from sqlmodel import SQLModel, Session, create_engine
from langflow.utils.logger import logger from langflow.utils.logger import logger
if settings.database_url and settings.database_url.startswith("sqlite"):
connect_args = {"check_same_thread": False} class Engine:
else: _instance = None
connect_args = {}
if not settings.database_url: @classmethod
raise RuntimeError("No database_url provided") def get(cls):
engine = create_engine(settings.database_url, connect_args=connect_args) logger.debug("Getting database engine")
if cls._instance is None:
cls.create()
return cls._instance
@classmethod
def create(cls):
logger.debug("Creating database engine")
from langflow.settings import settings
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
settings.DATABASE_URL = langflow_database_url
logger.debug("Using LANGFLOW_DATABASE_URL")
if settings.DATABASE_URL and settings.DATABASE_URL.startswith("sqlite"):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
if not settings.DATABASE_URL:
raise RuntimeError("No database_url provided")
cls._instance = create_engine(settings.DATABASE_URL, connect_args=connect_args)
@classmethod
def update(cls):
logger.debug("Updating database engine")
cls._instance = None
cls.create()
def create_db_and_tables(): def create_db_and_tables():
logger.debug("Creating database and tables") logger.debug("Creating database and tables")
try: try:
SQLModel.metadata.create_all(engine) SQLModel.metadata.create_all(Engine.get())
except Exception as exc: except Exception as exc:
logger.error(f"Error creating database and tables: {exc}") logger.error(f"Error creating database and tables: {exc}")
raise RuntimeError("Error creating database and tables") from exc raise RuntimeError("Error creating database and tables") from exc
@ -23,7 +50,7 @@ def create_db_and_tables():
# and we need to create the tables again. # and we need to create the tables again.
from sqlalchemy import inspect from sqlalchemy import inspect
inspector = inspect(engine) inspector = inspect(Engine.get())
if "flow" not in inspector.get_table_names(): if "flow" not in inspector.get_table_names():
logger.error("Something went wrong creating the database and tables.") logger.error("Something went wrong creating the database and tables.")
logger.error("Please check your database settings.") logger.error("Please check your database settings.")
@ -36,7 +63,7 @@ def create_db_and_tables():
@contextmanager @contextmanager
def session_getter(): def session_getter():
try: try:
session = Session(engine) session = Session(Engine.get())
yield session yield session
except Exception as e: except Exception as e:
print("Session rollback because of exception:", e) print("Session rollback because of exception:", e)

View file

@ -1,3 +1,4 @@
import ast
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
from langflow.interface.listing import ALL_TYPES_DICT from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.constants import DIRECT_TYPES from langflow.utils.constants import DIRECT_TYPES
@ -100,7 +101,9 @@ class Vertex:
params[param_key] = edge.source params[param_key] = edge.source
for key, value in template_dict.items(): for key, value in template_dict.items():
if key == "_type" or not value.get("show"): # Skip _type and any value that has show == False and is not code
# If we don't want to show code but we want to use it
if key == "_type" or (not value.get("show") and key != "code"):
continue continue
# If the type is not transformable to a python base class # If the type is not transformable to a python base class
# then we need to get the edge that connects to this node # then we need to get the edge that connects to this node
@ -112,7 +115,14 @@ class Vertex:
params[key] = file_path params[key] = file_path
elif value.get("type") in DIRECT_TYPES and params.get(key) is None: elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
params[key] = value.get("value") if value.get("type") == "code":
try:
params[key] = ast.literal_eval(value.get("value"))
except Exception as exc:
logger.debug(f"Error parsing code: {exc}")
params[key] = value.get("value")
else:
params[key] = value.get("value")
if not value.get("required") and params.get(key) is None: if not value.get("required") and params.get(key) is None:
if value.get("default"): if value.get("default"):
@ -259,4 +269,8 @@ class Vertex:
def _built_object_repr(self): def _built_object_repr(self):
# Add a message with an emoji, stars for sucess, # Add a message with an emoji, stars for sucess,
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵‍💫" return (
"Built sucessfully ✨"
if self._built_object is not None
else "Failed to build 😵‍💫"
)

View file

@ -226,7 +226,11 @@ class PromptVertex(Vertex):
# so the prompt format doesn't break # so the prompt format doesn't break
artifacts.pop("handle_keys", None) artifacts.pop("handle_keys", None)
try: try:
template = self._built_object.format(**artifacts) template = self._built_object.template
for key, value in artifacts.items():
if value:
replace_key = "{" + key + "}"
template = template.replace(replace_key, value)
return ( return (
template template
if isinstance(template, str) if isinstance(template, str)

View file

@ -59,7 +59,7 @@ class AgentCreator(LangChainTypeCreator):
if hasattr(agent, "function_name") if hasattr(agent, "function_name")
else agent.__name__ else agent.__name__
) )
if agent_name in settings.agents or settings.dev: if agent_name in settings.AGENTS or settings.DEV:
names.append(agent_name) names.append(agent_name)
return names return names

View file

@ -28,7 +28,7 @@ class LangChainTypeCreator(BaseModel, ABC):
"""A dict with the name of the component as key and the documentation link as value.""" """A dict with the name of the component as key and the documentation link as value."""
if self.name_docs_dict is None: if self.name_docs_dict is None:
try: try:
type_settings = getattr(settings, self.type_name) type_settings = getattr(settings, self.type_name.upper())
self.name_docs_dict = { self.name_docs_dict = {
name: value_dict["documentation"] name: value_dict["documentation"]
for name, value_dict in type_settings.items() for name, value_dict in type_settings.items()

View file

@ -8,6 +8,7 @@ from langflow.template.frontend_node.chains import ChainFrontendNode
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class, build_template_from_method from langflow.utils.util import build_template_from_class, build_template_from_method
from langchain import chains from langchain import chains
from langchain_experimental.sql import SQLDatabaseChain # type: ignore
# Assuming necessary imports for Field, Template, and FrontendNode classes # Assuming necessary imports for Field, Template, and FrontendNode classes
@ -35,12 +36,14 @@ class ChainCreator(LangChainTypeCreator):
} }
from langflow.interface.chains.custom import CUSTOM_CHAINS from langflow.interface.chains.custom import CUSTOM_CHAINS
self.type_dict["SQLDatabaseChain"] = SQLDatabaseChain
self.type_dict.update(CUSTOM_CHAINS) self.type_dict.update(CUSTOM_CHAINS)
# Filter according to settings.chains # Filter according to settings.chains
self.type_dict = { self.type_dict = {
name: chain name: chain
for name, chain in self.type_dict.items() for name, chain in self.type_dict.items()
if name in settings.chains or settings.dev if name in settings.CHAINS or settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -1,5 +1,5 @@
import ast import ast
from typing import Optional from typing import Any, Optional
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import HTTPException from fastapi import HTTPException
@ -63,10 +63,10 @@ class Component(BaseModel):
elif "description" in item_name: elif "description" in item_name:
template_config["description"] = ast.literal_eval(item_value) template_config["description"] = ast.literal_eval(item_value)
elif "field_config" in item_name: elif "beta" in item_name:
template_config["field_config"] = ast.literal_eval(item_value) template_config["beta"] = ast.literal_eval(item_value)
return template_config return template_config
def build(self): def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError raise NotImplementedError

View file

@ -7,6 +7,7 @@ from langchain.schema import BaseRetriever, Document
from langchain.text_splitter import TextSplitter from langchain.text_splitter import TextSplitter
from langchain.tools import Tool from langchain.tools import Tool
from langchain.vectorstores.base import VectorStore from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseOutputParser
LANGCHAIN_BASE_TYPES = { LANGCHAIN_BASE_TYPES = {
@ -20,6 +21,7 @@ LANGCHAIN_BASE_TYPES = {
"VectorStore": VectorStore, "VectorStore": VectorStore,
"Embeddings": Embeddings, "Embeddings": Embeddings,
"BaseRetriever": BaseRetriever, "BaseRetriever": BaseRetriever,
"BaseOutputParser": BaseOutputParser,
} }
# Langchain base types plus Python base types # Langchain base types plus Python base types

View file

@ -9,6 +9,7 @@ from langflow.utils import validate
from langflow.database.base import session_getter from langflow.database.base import session_getter
from langflow.database.models.flow import Flow from langflow.database.models.flow import Flow
from pydantic import Extra from pydantic import Extra
import yaml
class CustomComponent(Component, extra=Extra.allow): class CustomComponent(Component, extra=Extra.allow):
@ -24,6 +25,10 @@ class CustomComponent(Component, extra=Extra.allow):
super().__init__(**data) super().__init__(**data)
def custom_repr(self): def custom_repr(self):
if isinstance(self.repr_value, dict):
return yaml.dump(self.repr_value)
if isinstance(self.repr_value, str):
return self.repr_value
return str(self.repr_value) return str(self.repr_value)
def build_config(self): def build_config(self):
@ -190,5 +195,5 @@ class CustomComponent(Component, extra=Extra.allow):
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 self.load_flow(flow.id, tweaks)
def build(self): def build(self, *args: Any, **kwargs: Any) -> Any:
raise NotImplementedError raise NotImplementedError

View file

@ -1,6 +1,7 @@
import os import os
import ast import ast
import zlib import zlib
from langflow.utils.logger import logger
class CustomComponentPathValueError(ValueError): class CustomComponentPathValueError(ValueError):
@ -74,8 +75,11 @@ class DirectoryReader:
} }
for menu in data["menu"] for menu in data["menu"]
] ]
filtred = [menu for menu in items if menu["components"]] filtered = [menu for menu in items if menu["components"]]
return {"menu": filtred} logger.debug(
f'Filtered components {"with errors" if with_errors else ""}: {filtered}'
)
return {"menu": filtered}
def validate_code(self, file_content): def validate_code(self, file_content):
""" """
@ -116,7 +120,7 @@ class DirectoryReader:
file_list.extend( file_list.extend(
os.path.join(root, filename) os.path.join(root, filename)
for filename in files for filename in files
if filename.endswith(".py") if filename.endswith(".py") and not filename.startswith("__")
) )
return file_list return file_list
@ -213,27 +217,47 @@ class DirectoryReader:
from the .py files in the directory. from the .py files in the directory.
""" """
response = {"menu": []} response = {"menu": []}
logger.debug(
"-------------------- Building component menu list --------------------"
)
for file_path in file_paths: for file_path in file_paths:
menu_name = os.path.basename(os.path.dirname(file_path)) menu_name = os.path.basename(os.path.dirname(file_path))
logger.debug(f"Menu name: {menu_name}")
filename = os.path.basename(file_path) filename = os.path.basename(file_path)
validation_result, result_content = self.process_file(file_path) validation_result, result_content = self.process_file(file_path)
logger.debug(f"Validation result: {validation_result}")
menu_result = self.find_menu(response, menu_name) or { menu_result = self.find_menu(response, menu_name) or {
"name": menu_name, "name": menu_name,
"path": os.path.dirname(file_path), "path": os.path.dirname(file_path),
"components": [], "components": [],
} }
component_name = filename.split(".")[0]
# This is the name of the file which will be displayed in the UI
# We need to change it from snake_case to CamelCase
# first check if it's already CamelCase
if "_" in component_name:
component_name_camelcase = " ".join(
word.title() for word in component_name.split("_")
)
else:
component_name_camelcase = component_name
component_info = { component_info = {
"name": filename.split(".")[0], "name": "CustomComponent",
"output_types": [component_name_camelcase],
"file": filename, "file": filename,
"code": result_content if validation_result else "", "code": result_content if validation_result else "",
"error": "" if validation_result else result_content, "error": "" if validation_result else result_content,
} }
menu_result["components"].append(component_info) menu_result["components"].append(component_info)
logger.debug(f"Component info: {component_info}")
if menu_result not in response["menu"]: if menu_result not in response["menu"]:
response["menu"].append(menu_result) response["menu"].append(menu_result)
logger.debug(
"-------------------- Component menu list built --------------------"
)
return response return response

View file

@ -33,7 +33,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
return [ return [
documentloader.__name__ documentloader.__name__
for documentloader in self.type_to_loader_dict.values() for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings.documentloaders or settings.dev if documentloader.__name__ in settings.DOCUMENTLOADERS or settings.DEV
] ]

View file

@ -35,7 +35,7 @@ class EmbeddingCreator(LangChainTypeCreator):
return [ return [
embedding.__name__ embedding.__name__
for embedding in self.type_to_loader_dict.values() for embedding in self.type_to_loader_dict.values()
if embedding.__name__ in settings.embeddings or settings.dev if embedding.__name__ in settings.EMBEDDINGS or settings.DEV
] ]

View file

@ -61,9 +61,7 @@ def import_by_type(_type: str, name: str) -> Any:
def import_custom_component(custom_component: str) -> CustomComponent: def import_custom_component(custom_component: str) -> CustomComponent:
"""Import custom component from custom component name""" """Import custom component from custom component name"""
return import_class( return import_class("langflow.interface.custom.custom_component.CustomComponent")
f"langflow.interface.custom.custom_component.{custom_component}"
)
def import_output_parser(output_parser: str) -> Any: def import_output_parser(output_parser: str) -> Any:

View file

@ -6,7 +6,11 @@ from langchain.agents.agent import AgentExecutor
from langchain.agents.agent_toolkits.base import BaseToolkit from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.agents.tools import BaseTool from langchain.agents.tools import BaseTool
from 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 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
@ -29,6 +33,7 @@ from langflow.utils import validate
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.vectorstores.base import VectorStore from langchain.vectorstores.base import VectorStore
from langchain.document_loaders.base import BaseLoader from langchain.document_loaders.base import BaseLoader
from langflow.utils.logger import logger
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
@ -40,7 +45,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
if hasattr(custom_node, "initialize"): if hasattr(custom_node, "initialize"):
return custom_node.initialize(**params) return custom_node.initialize(**params)
return custom_node(**params) return custom_node(**params)
logger.debug(f"Instantiating {node_type} of type {base_type}")
class_object = import_by_type(_type=base_type, name=node_type) class_object = import_by_type(_type=base_type, name=node_type)
return instantiate_based_on_type(class_object, base_type, node_type, params) return instantiate_based_on_type(class_object, base_type, node_type, params)
@ -217,6 +222,9 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
def instantiate_prompt(node_type, class_object, params: Dict): def instantiate_prompt(node_type, class_object, params: Dict):
params, prompt = handle_node_type(node_type, class_object, params) params, prompt = handle_node_type(node_type, class_object, params)
format_kwargs = handle_format_kwargs(prompt, params) format_kwargs = handle_format_kwargs(prompt, params)
# Now we'll use partial_format to format the prompt
if format_kwargs:
prompt = handle_partial_variables(prompt, format_kwargs)
return prompt, format_kwargs return prompt, format_kwargs

View file

@ -44,6 +44,16 @@ def handle_format_kwargs(prompt, params: Dict):
return format_kwargs return format_kwargs
def handle_partial_variables(prompt, format_kwargs: Dict):
partial_variables = format_kwargs.copy()
partial_variables = {
key: value for key, value in partial_variables.items() if value
}
# Remove handle_keys otherwise LangChain raises an error
partial_variables.pop("handle_keys", None)
return prompt.partial(**partial_variables)
def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict): def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
variable = params[input_variable] variable = params[input_variable]
if isinstance(variable, str): if isinstance(variable, str):

View file

@ -36,7 +36,7 @@ class LLMCreator(LangChainTypeCreator):
return [ return [
llm.__name__ llm.__name__
for llm in self.type_to_loader_dict.values() for llm in self.type_to_loader_dict.values()
if llm.__name__ in settings.llms or settings.dev if llm.__name__ in settings.LLMS or settings.DEV
] ]

View file

@ -51,7 +51,7 @@ class MemoryCreator(LangChainTypeCreator):
return [ return [
memory.__name__ memory.__name__
for memory in self.type_to_loader_dict.values() for memory in self.type_to_loader_dict.values()
if memory.__name__ in settings.memories or settings.dev if memory.__name__ in settings.MEMORIES or settings.DEV
] ]

View file

@ -33,7 +33,7 @@ class OutputParserCreator(LangChainTypeCreator):
self.type_dict = { self.type_dict = {
name: output_parser name: output_parser
for name, output_parser in self.type_dict.items() for name, output_parser in self.type_dict.items()
if name in settings.output_parsers or settings.dev if name in settings.OUTPUT_PARSERS or settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -34,7 +34,7 @@ class PromptCreator(LangChainTypeCreator):
self.type_dict = { self.type_dict = {
name: prompt name: prompt
for name, prompt in self.type_dict.items() for name, prompt in self.type_dict.items()
if name in settings.prompts or settings.dev if name in settings.PROMPTS or settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -51,7 +51,7 @@ class RetrieverCreator(LangChainTypeCreator):
return [ return [
retriever retriever
for retriever in self.type_to_loader_dict.keys() for retriever in self.type_to_loader_dict.keys()
if retriever in settings.retrievers or settings.dev if retriever in settings.RETRIEVERS or settings.DEV
] ]

View file

@ -33,7 +33,7 @@ class TextSplitterCreator(LangChainTypeCreator):
return [ return [
textsplitter.__name__ textsplitter.__name__
for textsplitter in self.type_to_loader_dict.values() for textsplitter in self.type_to_loader_dict.values()
if textsplitter.__name__ in settings.textsplitters or settings.dev if textsplitter.__name__ in settings.TEXTSPLITTERS or settings.DEV
] ]

View file

@ -35,7 +35,7 @@ class ToolkitCreator(LangChainTypeCreator):
) )
# if toolkit_name is not lower case it is a class # if toolkit_name is not lower case it is a class
for toolkit_name in agent_toolkits.__all__ for toolkit_name in agent_toolkits.__all__
if not toolkit_name.islower() and toolkit_name in settings.toolkits if not toolkit_name.islower() and toolkit_name in settings.TOOLKITS
} }
return self.type_dict return self.type_dict

View file

@ -74,7 +74,7 @@ class ToolCreator(LangChainTypeCreator):
tool_name = tool_params.get("name") or tool tool_name = tool_params.get("name") or tool
if tool_name in settings.tools or settings.dev: if tool_name in settings.TOOLS or settings.DEV:
if tool_name == "JsonSpec": if tool_name == "JsonSpec":
tool_params["path"] = tool_params.pop("dict_") # type: ignore tool_params["path"] = tool_params.pop("dict_") # type: ignore
all_tools[tool_name] = { all_tools[tool_name] = {

View file

@ -1,3 +1,5 @@
import ast
import contextlib
from typing import Any from typing import Any
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
@ -145,7 +147,7 @@ def add_code_field(template, raw_code, field_config):
"dynamic": True, "dynamic": True,
"required": True, "required": True,
"placeholder": "", "placeholder": "",
"show": True, "show": field_config.pop("show", True),
"multiline": True, "multiline": True,
"value": raw_code, "value": raw_code,
"password": False, "password": False,
@ -186,7 +188,7 @@ def build_frontend_node(custom_component: CustomComponent):
return None return None
def update_display_name_and_description(frontend_node, template_config): def update_attributes(frontend_node, template_config):
"""Update the display name and description of a frontend node""" """Update the display name and description of a frontend node"""
if "display_name" in template_config: if "display_name" in template_config:
frontend_node["display_name"] = template_config["display_name"] frontend_node["display_name"] = template_config["display_name"]
@ -194,6 +196,9 @@ def update_display_name_and_description(frontend_node, template_config):
if "description" in template_config: if "description" in template_config:
frontend_node["description"] = template_config["description"] frontend_node["description"] = template_config["description"]
if "beta" in template_config:
frontend_node["beta"] = template_config["beta"]
def build_field_config(custom_component: CustomComponent): def build_field_config(custom_component: CustomComponent):
"""Build the field configuration for a custom component""" """Build the field configuration for a custom component"""
@ -247,6 +252,8 @@ def get_field_properties(extra_field):
if not field_required: if not field_required:
field_type = extract_type_from_optional(field_type) field_type = extract_type_from_optional(field_type)
with contextlib.suppress(Exception):
field_value = ast.literal_eval(field_value)
return field_name, field_type, field_value, field_required return field_name, field_type, field_value, field_required
@ -274,28 +281,30 @@ def add_base_classes(frontend_node, return_type):
def build_langchain_template_custom_component(custom_component: CustomComponent): def build_langchain_template_custom_component(custom_component: CustomComponent):
"""Build a custom component template for the langchain""" """Build a custom component template for the langchain"""
logger.debug("Building custom component template")
frontend_node = build_frontend_node(custom_component) frontend_node = build_frontend_node(custom_component)
if frontend_node is None: if frontend_node is None:
return None return None
logger.debug("Built base frontend node")
template_config = custom_component.build_template_config template_config = custom_component.build_template_config
update_display_name_and_description(frontend_node, template_config) update_attributes(frontend_node, template_config)
logger.debug("Updated attributes")
field_config = build_field_config(custom_component) field_config = build_field_config(custom_component)
logger.debug("Built field config")
add_extra_fields( add_extra_fields(
frontend_node, field_config, custom_component.get_function_entrypoint_args frontend_node, field_config, custom_component.get_function_entrypoint_args
) )
logger.debug("Added extra fields")
frontend_node = add_code_field( frontend_node = add_code_field(
frontend_node, custom_component.code, field_config.get("code", {}) frontend_node, custom_component.code, field_config.get("code", {})
) )
logger.debug("Added code field")
add_base_classes( add_base_classes(
frontend_node, custom_component.get_function_entrypoint_return_type frontend_node, custom_component.get_function_entrypoint_return_type
) )
logger.debug("Added base classes")
return frontend_node return frontend_node
@ -306,7 +315,7 @@ def load_files_from_path(path: str):
return reader.get_files() return reader.get_files()
def build_and_validate_all_files(reader, file_list): def build_and_validate_all_files(reader: DirectoryReader, file_list):
"""Build and validate all files""" """Build and validate all files"""
data = reader.build_component_menu_list(file_list) data = reader.build_component_menu_list(file_list)
@ -319,31 +328,53 @@ def build_and_validate_all_files(reader, file_list):
def build_valid_menu(valid_components): def build_valid_menu(valid_components):
"""Build the valid menu""" """Build the valid menu"""
valid_menu = {} valid_menu = {}
logger.debug("------------------- 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] = {}
for component in menu_item["components"]: for component in menu_item["components"]:
logger.debug(f"Building component: {component}")
try: try:
component_name = component["name"] component_name = component["name"]
component_code = component["code"] component_code = component["code"]
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.is_check_valid()
component_template = build_langchain_template_custom_component( component_template = build_langchain_template_custom_component(
component_extractor component_extractor
) )
component_template["output_types"] = component_output_types
if len(component_output_types) == 1:
component_name = component_output_types[0]
else:
file_name = component.get("file").split(".")[0]
if "_" in file_name:
# turn .py file into camelcase
component_name = "".join(
[word.capitalize() for word in file_name.split("_")]
)
else:
component_name = file_name
valid_menu[menu_name][component_name] = component_template valid_menu[menu_name][component_name] = component_template
logger.debug(f"Added {component_name} to valid menu to {menu_name}")
except Exception as exc: except Exception as exc:
logger.error(f"Error while building custom component: {exc}") logger.error(f"Error loading Component: {component['output_types']}")
logger.exception(
f"Error while building custom component {component_output_types}: {exc}"
)
return valid_menu return valid_menu
def build_invalid_menu(invalid_components): def build_invalid_menu(invalid_components):
"""Build the invalid menu""" """Build the invalid menu"""
if invalid_components.get("menu"):
logger.debug("------------------- INVALID COMPONENTS -------------------")
invalid_menu = {} invalid_menu = {}
for menu_item in invalid_components["menu"]: for menu_item in invalid_components["menu"]:
menu_name = menu_item["name"] menu_name = menu_item["name"]
@ -364,12 +395,16 @@ def build_invalid_menu(invalid_components):
) )
component_template["error"] = component.get("error", None) component_template["error"] = component.get("error", None)
logger.debug(component)
logger.debug(f"Component Path: {component.get('path', None)}")
logger.debug(f"Component Error: {component.get('error', None)}")
component_template.get("template").get("code")["value"] = component_code component_template.get("template").get("code")["value"] = component_code
invalid_menu[menu_name][component_name] = component_template invalid_menu[menu_name][component_name] = component_template
logger.debug(f"Added {component_name} to invalid menu to {menu_name}")
except Exception as exc: except Exception as exc:
logger.error( logger.exception(
f"Error while creating custom component [{component_name}]: {str(exc)}" f"Error while creating custom component [{component_name}]: {str(exc)}"
) )

View file

@ -35,7 +35,7 @@ class UtilityCreator(LangChainTypeCreator):
self.type_dict = { self.type_dict = {
name: utility name: utility
for name, utility in self.type_dict.items() for name, utility in self.type_dict.items()
if name in settings.utilities or settings.dev if name in settings.UTILITIES or settings.DEV
} }
return self.type_dict return self.type_dict

View file

@ -69,18 +69,17 @@ def setup_llm_caching():
try: try:
set_langchain_cache(settings) set_langchain_cache(settings)
except ImportError: except ImportError:
logger.warning(f"Could not import {settings.cache}. ") logger.warning(f"Could not import {settings.CACHE}. ")
except Exception as exc: except Exception as exc:
logger.warning(f"Could not setup LLM caching. Error: {exc}") logger.warning(f"Could not setup LLM caching. Error: {exc}")
# TODO Rename this here and in `setup_llm_caching`
def set_langchain_cache(settings): def set_langchain_cache(settings):
import langchain import langchain
from langflow.interface.importing.utils import import_class from langflow.interface.importing.utils import import_class
cache_type = os.getenv("LANGFLOW_LANGCHAIN_CACHE") cache_type = os.getenv("LANGFLOW_LANGCHAIN_CACHE")
cache_class = import_class(f"langchain.cache.{cache_type or settings.cache}") cache_class = import_class(f"langchain.cache.{cache_type or settings.CACHE}")
logger.debug(f"Setting up LLM caching with {cache_class.__name__}") logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
langchain.llm_cache = cache_class() langchain.llm_cache = cache_class()

View file

@ -46,7 +46,7 @@ class VectorstoreCreator(LangChainTypeCreator):
return [ return [
vectorstore vectorstore
for vectorstore in self.type_to_loader_dict.keys() for vectorstore in self.type_to_loader_dict.keys()
if vectorstore in settings.vectorstores or settings.dev if vectorstore in settings.VECTORSTORES or settings.DEV
] ]

View file

@ -6,7 +6,7 @@ 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.database.base import create_db_and_tables from langflow.database.base import create_db_and_tables, Engine
from langflow.interface.utils import setup_llm_caching from langflow.interface.utils import setup_llm_caching
from langflow.utils.logger import configure from langflow.utils.logger import configure
@ -34,6 +34,7 @@ def create_app():
) )
app.include_router(router) app.include_router(router)
app.on_event("startup")(Engine.update)
app.on_event("startup")(create_db_and_tables) app.on_event("startup")(create_db_and_tables)
app.on_event("startup")(setup_llm_caching) app.on_event("startup")(setup_llm_caching)
return app return app

View file

@ -1,71 +1,91 @@
import contextlib
import json
import os import os
from typing import Optional, List from typing import Optional, List
from pathlib import Path from pathlib import Path
import yaml import yaml
from pydantic import BaseSettings, root_validator from pydantic import BaseSettings, root_validator, validator
from langflow.utils.logger import logger from langflow.utils.logger import logger
BASE_COMPONENTS_PATH = Path(__file__).parent / "components" BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
class Settings(BaseSettings): class Settings(BaseSettings):
chains: dict = {} CHAINS: dict = {}
agents: dict = {} AGENTS: dict = {}
prompts: dict = {} PROMPTS: dict = {}
llms: dict = {} LLMS: dict = {}
tools: dict = {} TOOLS: dict = {}
memories: dict = {} MEMORIES: dict = {}
embeddings: dict = {} EMBEDDINGS: dict = {}
vectorstores: dict = {} VECTORSTORES: dict = {}
documentloaders: dict = {} DOCUMENTLOADERS: dict = {}
wrappers: dict = {} WRAPPERS: dict = {}
retrievers: dict = {} RETRIEVERS: dict = {}
toolkits: dict = {} TOOLKITS: dict = {}
textsplitters: dict = {} TEXTSPLITTERS: dict = {}
utilities: dict = {} UTILITIES: dict = {}
output_parsers: dict = {} OUTPUT_PARSERS: dict = {}
custom_components: dict = {} CUSTOM_COMPONENTS: dict = {}
dev: bool = False DEV: bool = False
database_url: Optional[str] = None DATABASE_URL: Optional[str] = None
cache: str = "InMemoryCache" CACHE: str = "InMemoryCache"
remove_api_keys: bool = False REMOVE_API_KEYS: bool = False
components_path: List[Path] COMPONENTS_PATH: List[str] = []
@root_validator(pre=True) @validator("DATABASE_URL", pre=True)
def set_env_variables(cls, values): def set_database_url(cls, value):
if "database_url" not in values: if not value:
logger.debug( logger.debug(
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable" "No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
) )
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"): if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
values["database_url"] = langflow_database_url value = langflow_database_url
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
else: else:
logger.debug("No DATABASE_URL env variable, using sqlite database") logger.debug("No DATABASE_URL env variable, using sqlite database")
values["database_url"] = "sqlite:///./langflow.db" value = "sqlite:///./langflow.db"
if not values.get("components_path"): return value
values["components_path"] = [BASE_COMPONENTS_PATH]
logger.debug("No components_path provided, using default components path")
elif BASE_COMPONENTS_PATH not in values["components_path"]:
values["components_path"].append(BASE_COMPONENTS_PATH)
logger.debug("Adding default components path to components_path")
@validator("COMPONENTS_PATH", pre=True)
def set_components_path(cls, value):
if os.getenv("LANGFLOW_COMPONENTS_PATH"): if os.getenv("LANGFLOW_COMPONENTS_PATH"):
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path") logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
langflow_component_path = Path(os.getenv("LANGFLOW_COMPONENTS_PATH")) langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
if ( if (
langflow_component_path.exists() Path(langflow_component_path).exists()
and langflow_component_path not in values["components_path"] and langflow_component_path not in value
): ):
values["components_path"].append(langflow_component_path) if isinstance(langflow_component_path, list):
logger.debug(f"Adding {langflow_component_path} to components_path") for path in langflow_component_path:
return values if path not in value:
value.append(path)
logger.debug(
f"Extending {langflow_component_path} to components_path"
)
elif langflow_component_path not in value:
value.append(langflow_component_path)
logger.debug(
f"Appending {langflow_component_path} to components_path"
)
if not value:
value = [BASE_COMPONENTS_PATH]
logger.debug("Setting default components path to components_path")
elif BASE_COMPONENTS_PATH not in value:
value.append(BASE_COMPONENTS_PATH)
logger.debug("Adding default components path to components_path")
logger.debug(f"Components path: {value}")
return value
class Config: class Config:
validate_assignment = True validate_assignment = True
extra = "ignore" extra = "ignore"
env_prefix = "LANGFLOW_"
@root_validator(allow_reuse=True) @root_validator(allow_reuse=True)
def validate_lists(cls, values): def validate_lists(cls, values):
@ -76,35 +96,50 @@ class Settings(BaseSettings):
def update_from_yaml(self, file_path: str, dev: bool = False): def update_from_yaml(self, file_path: str, dev: bool = False):
new_settings = load_settings_from_yaml(file_path) new_settings = load_settings_from_yaml(file_path)
self.chains = new_settings.chains or {} self.CHAINS = new_settings.CHAINS or {}
self.agents = new_settings.agents or {} self.AGENTS = new_settings.AGENTS or {}
self.prompts = new_settings.prompts or {} self.PROMPTS = new_settings.PROMPTS or {}
self.llms = new_settings.llms or {} self.LLMS = new_settings.LLMS or {}
self.tools = new_settings.tools or {} self.TOOLS = new_settings.TOOLS or {}
self.memories = new_settings.memories or {} self.MEMORIES = new_settings.MEMORIES or {}
self.wrappers = new_settings.wrappers or {} self.WRAPPERS = new_settings.WRAPPERS or {}
self.toolkits = new_settings.toolkits or {} self.TOOLKITS = new_settings.TOOLKITS or {}
self.textsplitters = new_settings.textsplitters or {} self.TEXTSPLITTERS = new_settings.TEXTSPLITTERS or {}
self.utilities = new_settings.utilities or {} self.UTILITIES = new_settings.UTILITIES or {}
self.embeddings = new_settings.embeddings or {} self.EMBEDDINGS = new_settings.EMBEDDINGS or {}
self.vectorstores = new_settings.vectorstores or {} self.VECTORSTORES = new_settings.VECTORSTORES or {}
self.documentloaders = new_settings.documentloaders or {} self.DOCUMENTLOADERS = new_settings.DOCUMENTLOADERS or {}
self.retrievers = new_settings.retrievers or {} self.RETRIEVERS = new_settings.RETRIEVERS or {}
self.output_parsers = new_settings.output_parsers or {} self.OUTPUT_PARSERS = new_settings.OUTPUT_PARSERS or {}
self.custom_components = new_settings.custom_components or {} self.CUSTOM_COMPONENTS = new_settings.CUSTOM_COMPONENTS or {}
self.components_path = new_settings.components_path or [] self.COMPONENTS_PATH = new_settings.COMPONENTS_PATH or []
self.dev = dev self.DEV = dev
def update_settings(self, **kwargs): def update_settings(self, **kwargs):
logger.debug("Updating settings")
for key, value in kwargs.items(): for key, value in kwargs.items():
if hasattr(self, key): # value may contain sensitive information, so we don't want to log it
if isinstance(getattr(self, key), list): if not hasattr(self, key):
if isinstance(value, list): logger.debug(f"Key {key} not found in settings")
getattr(self, key).extend(value) continue
else: logger.debug(f"Updating {key}")
getattr(self, key).append(value) if isinstance(getattr(self, key), list):
# value might be a '[something]' string
with contextlib.suppress(json.decoder.JSONDecodeError):
value = json.loads(str(value))
if isinstance(value, list):
for item in value:
if item not in getattr(self, key):
getattr(self, key).append(item)
logger.debug(f"Extended {key}")
else: else:
setattr(self, key, value) getattr(self, key).append(value)
logger.debug(f"Appended {key}")
else:
setattr(self, key, value)
logger.debug(f"Updated {key}")
logger.debug(f"{key}: {getattr(self, key)}")
def save_settings_to_yaml(settings: Settings, file_path: str): def save_settings_to_yaml(settings: Settings, file_path: str):
@ -123,6 +158,12 @@ def load_settings_from_yaml(file_path: str) -> Settings:
with open(file_path, "r") as f: with open(file_path, "r") as f:
settings_dict = yaml.safe_load(f) settings_dict = yaml.safe_load(f)
settings_dict = {k.upper(): v for k, v in settings_dict.items()}
for key in settings_dict:
if key not in Settings.__fields__.keys():
raise KeyError(f"Key {key} not found in settings")
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
return Settings(**settings_dict) return Settings(**settings_dict)

View file

@ -0,0 +1 @@
**/node_modules

View file

@ -87,7 +87,7 @@ export default function ParameterComponent({
useEffect(() => { useEffect(() => {
if (name === "openai_api_base") console.log(info); if (name === "openai_api_base") console.log(info);
infoHtml.current = ( infoHtml.current = (
<div className="h-full w-full break-words"> <div className="h-full w-full word-break-break-word">
{info.split("\n").map((line, i) => ( {info.split("\n").map((line, i) => (
<p key={i} className="block"> <p key={i} className="block">
{line} {line}

View file

@ -36,14 +36,14 @@ export default function SingleAlert({
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="break-words text-sm font-medium text-error-foreground"> <h3 className="text-sm font-medium text-error-foreground word-break-break-word">
{dropItem.title} {dropItem.title}
</h3> </h3>
{dropItem.list ? ( {dropItem.list ? (
<div className="mt-2 text-sm text-error-foreground"> <div className="mt-2 text-sm text-error-foreground">
<ul className="list-disc space-y-1 pl-5"> <ul className="list-disc space-y-1 pl-5">
{dropItem.list.map((item, idx) => ( {dropItem.list.map((item, idx) => (
<li className="break-words" key={idx}> <li className="word-break-break-word" key={idx}>
{item} {item}
</li> </li>
))} ))}

View file

@ -47,7 +47,9 @@ export default function NoticeAlert({
/> />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground">{title}</p> <p className="text-sm text-info-foreground word-break-break-word">
{title}
</p>
<p className="mt-3 text-sm md:ml-6 md:mt-0"> <p className="mt-3 text-sm md:ml-6 md:mt-0">
{link !== "" ? ( {link !== "" ? (
<Link <Link

View file

@ -132,21 +132,25 @@ export default function CodeTabsComponent({
}} }}
> >
<div className="api-modal-tablist-div"> <div className="api-modal-tablist-div">
<TabsList> {tabs.length > 0 && tabs[0].name !== "" ? (
{tabs.map((tab, index) => ( <TabsList>
<TabsTrigger {tabs.map((tab, index) => (
className={ <TabsTrigger
isMessage ? "data-[state=active]:bg-primary-foreground" : "" className={
} isMessage ? "data-[state=active]:bg-primary-foreground" : ""
key={index} }
value={index.toString()} key={index}
> value={index.toString()}
{tab.name} >
</TabsTrigger> {tab.name}
))} </TabsTrigger>
</TabsList> ))}
</TabsList>
) : (
<div></div>
)}
{Number(activeTab) < 4 && ( {Number(activeTab) < 4 && (
<div className="float-right mx-1 flex gap-2"> <div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<button <button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300" className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard} onClick={copyToClipboard}

View file

@ -180,8 +180,8 @@ export default function CodeAreaModal({
<h1 className="text-lg text-destructive"> <h1 className="text-lg text-destructive">
{error?.detail?.error} {error?.detail?.error}
</h1> </h1>
<div className="ml-2 w-full break-all text-sm text-status-red"> <div className="ml-2 w-full text-sm text-status-red word-break-break-word">
<pre className="w-full whitespace-pre-wrap break-all"> <pre className="w-full word-break-break-word">
{error?.detail?.traceback} {error?.detail?.traceback}
</pre> </pre>
</div> </div>

View file

@ -13,6 +13,7 @@ const ExportModal = forwardRef((props: { children: ReactNode }, ref) => {
useContext(TabsContext); useContext(TabsContext);
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [name, setName] = useState(flows.find((f) => f.id === tabId).name); const [name, setName] = useState(flows.find((f) => f.id === tabId).name);
const [invalidName, setInvalidName] = useState(false);
const [description, setDescription] = useState( const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description flows.find((f) => f.id === tabId).description
); );
@ -30,6 +31,8 @@ const ExportModal = forwardRef((props: { children: ReactNode }, ref) => {
</BaseModal.Header> </BaseModal.Header>
<BaseModal.Content> <BaseModal.Content>
<EditFlowSettings <EditFlowSettings
invalidName={invalidName}
setInvalidName={setInvalidName}
name={name} name={name}
description={description} description={description}
flows={flows} flows={flows}

View file

@ -88,8 +88,8 @@ export default function ChatMessage({
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]} rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary dark:prose-invert className="markdown prose min-w-full text-primary word-break-break-word
sm:w-[30vw] sm:max-w-[30vw] lg:w-[40vw] lg:max-w-[40vw]" dark:prose-invert"
components={{ components={{
pre({ node, ...props }) { pre({ node, ...props }) {
return <>{props.children}</>; return <>{props.children}</>;
@ -187,7 +187,7 @@ export default function ChatMessage({
} }
/> />
</button> </button>
<span className="prose inline-block break-words text-primary dark:prose-invert"> <span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen {promptOpen
? template?.split("\n")?.map((line, index) => { ? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g; const regex = /{([^}]+)}/g;

View file

@ -96,7 +96,7 @@ export default function ButtonBox({
<div className="mb-auto mt-auto w-full"> <div className="mb-auto mt-auto w-full">
<h3 <h3
className={classNames( className={classNames(
"w-full break-words font-semibold text-background truncate-multiline", "w-full font-semibold text-background truncate-multiline word-break-break-word",
titleFontSize, titleFontSize,
marginTop marginTop
)} )}

View file

@ -301,13 +301,13 @@
} }
.error-build-message { .error-build-message {
@apply mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl; @apply mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl;
} }
.error-build-message-circle { .error-build-message-circle {
@apply alert-icon text-status-red; @apply alert-icon text-status-red;
} }
.error-build-text { .error-build-text {
@apply text-error-foreground; @apply text-error-foreground word-break-break-word;
} }
.error-build-foreground { .error-build-foreground {
@apply error-build-text alert-font-size; @apply error-build-text alert-font-size;
@ -326,7 +326,7 @@
@apply alert-icon text-status-green; @apply alert-icon text-status-green;
} }
.success-alert-message { .success-alert-message {
@apply alert-font-size text-success-foreground; @apply word-break-break-word alert-font-size text-success-foreground;
} }
.card-component-title-display { .card-component-title-display {
@ -336,7 +336,7 @@
@apply flex h-7 w-7 items-center justify-center rounded-full text-2xl; @apply flex h-7 w-7 items-center justify-center rounded-full text-2xl;
} }
.card-component-title-size { .card-component-title-size {
@apply inline-block w-full flex-1 break-words truncate-doubleline; @apply w-full flex-1 word-break-break-word truncate-doubleline;
} }
.card-component-delete-button { .card-component-delete-button {
@apply flex self-start; @apply flex self-start;
@ -544,7 +544,7 @@
@apply focus:outline-none focus:ring-1 focus:ring-primary focus:ring-offset-1; @apply focus:outline-none focus:ring-1 focus:ring-primary focus:ring-offset-1;
} }
.toggle-component-span { .toggle-component-span {
@apply pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out; @apply pointer-events-none relative h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out;
} }
.toggle-component-second-span { .toggle-component-second-span {
@apply absolute inset-0 flex h-full w-full items-center justify-center transition-opacity; @apply absolute inset-0 flex h-full w-full items-center justify-center transition-opacity;
@ -590,13 +590,13 @@
@apply flex-max-width items-center text-start; @apply flex-max-width items-center text-start;
} }
.chat-message-modal-text { .chat-message-modal-text {
@apply relative inline-block w-full text-start text-sm font-normal text-muted-foreground; @apply relative w-full text-start text-sm font-normal text-muted-foreground;
} }
.chat-message-modal-icon-div { .chat-message-modal-icon-div {
@apply absolute -left-2 -top-1 cursor-pointer; @apply absolute -left-2 -top-1 cursor-pointer;
} }
.chat-message-modal-thought { .chat-message-modal-thought {
@apply chat-message-modal-thought-cursor ml-3 inline-block h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground; @apply chat-message-modal-thought-cursor ml-3 h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground;
} }
.chat-message-modal-thought-cursor { .chat-message-modal-thought-cursor {
@apply cursor-pointer overflow-scroll scrollbar-hide; @apply cursor-pointer overflow-scroll scrollbar-hide;
@ -608,7 +608,7 @@
@apply mt-1 animate-pulse cursor-default; @apply mt-1 animate-pulse cursor-default;
} }
.chat-message-modal-alert { .chat-message-modal-alert {
@apply inline-block px-3 text-start text-muted-foreground; @apply px-3 text-start text-muted-foreground;
} }
.file-card-modal-image-div { .file-card-modal-image-div {
@ -852,11 +852,11 @@
@apply bg-white text-primary; @apply bg-white text-primary;
} }
.code-highlight { .code-highlight {
@apply block max-h-[64vh] w-full overflow-y-hidden break-all border-0 px-3 py-2 text-sm outline-0; @apply block max-h-[64vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
} }
.code-nohighlight { .code-nohighlight {
@apply block max-h-[70vh] w-full overflow-y-hidden break-all border-0 px-3 py-2 text-sm outline-0; @apply block max-h-[70vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
} }
.form-modal-lockchat { .form-modal-lockchat {
@apply form-input block w-full rounded-md border-border p-4 pr-16 custom-scroll focus:border-ring focus:ring-ring sm:text-sm; @apply form-input block w-full rounded-md border-border p-4 pr-16 custom-scroll focus:border-ring focus:ring-ring sm:text-sm;
@ -913,7 +913,7 @@
@apply rounded-md border border-ring/60; @apply rounded-md border border-ring/60;
} }
.form-modal-chat-thought-size { .form-modal-chat-thought-size {
@apply inline-block h-full w-[95%]; @apply h-full w-[95%];
} }
.form-modal-chat-thought { .form-modal-chat-thought {
@apply form-modal-chat-thought-border form-modal-chat-thought-size cursor-pointer overflow-scroll bg-background px-2 py-2 text-start text-primary scrollbar-hide; @apply form-modal-chat-thought-border form-modal-chat-thought-size cursor-pointer overflow-scroll bg-background px-2 py-2 text-start text-primary scrollbar-hide;

View file

@ -155,7 +155,9 @@ module.exports = {
overflow: "hidden", overflow: "hidden",
"text-overflow": "ellipsis", "text-overflow": "ellipsis",
}, },
".word-break-break-word": {
wordBreak: "break-word",
},
".arrow-hide": { ".arrow-hide": {
"&::-webkit-inner-spin-button": { "&::-webkit-inner-spin-button": {
"-webkit-appearance": "none", "-webkit-appearance": "none",

View file

@ -12,7 +12,6 @@ from langflow.graph.vertex.types import (
FileToolVertex, FileToolVertex,
LLMVertex, LLMVertex,
ToolkitVertex, ToolkitVertex,
WrapperVertex,
) )
from langflow.processing.process import get_result_and_thought from langflow.processing.process import get_result_and_thought
from langflow.utils.payload import get_root_node from langflow.utils.payload import get_root_node
@ -292,11 +291,11 @@ def test_file_tool_node_build(openapi_graph):
assert not Path(file_path).exists() assert not Path(file_path).exists()
def test_wrapper_node_build(openapi_graph): # def test_wrapper_node_build(openapi_graph):
wrapper_node = get_node_by_type(openapi_graph, WrapperVertex) # wrapper_node = get_node_by_type(openapi_graph, WrapperVertex)
assert wrapper_node is not None # assert wrapper_node is not None
built_object = wrapper_node.build() # built_object = wrapper_node.build()
assert built_object is not None # assert built_object is not None
def test_get_result_and_thought(basic_graph): def test_get_result_and_thought(basic_graph):

View file

@ -7,7 +7,7 @@ def test_llms_settings(client: TestClient):
assert response.status_code == 200 assert response.status_code == 200
json_response = response.json() json_response = response.json()
llms = json_response["llms"] llms = json_response["llms"]
assert set(llms.keys()) == set(settings.llms) assert set(llms.keys()) == set(settings.LLMS)
# def test_hugging_face_hub(client: TestClient): # def test_hugging_face_hub(client: TestClient):

View file

@ -7,7 +7,7 @@ def test_prompts_settings(client: TestClient):
assert response.status_code == 200 assert response.status_code == 200
json_response = response.json() json_response = response.json()
prompts = json_response["prompts"] prompts = json_response["prompts"]
assert set(prompts.keys()) == set(settings.prompts) assert set(prompts.keys()) == set(settings.PROMPTS)
def test_prompt_template(client: TestClient): def test_prompt_template(client: TestClient):

View file

@ -9,4 +9,4 @@ def test_vectorstores_settings(client: TestClient):
assert response.status_code == 200 assert response.status_code == 200
json_response = response.json() json_response = response.json()
vectorstores = json_response["vectorstores"] vectorstores = json_response["vectorstores"]
assert set(vectorstores.keys()) == set(settings.vectorstores) assert set(vectorstores.keys()) == set(settings.VECTORSTORES)