Merge remote-tracking branch 'origin/dev' into authentication
This commit is contained in:
commit
4af08d99fc
132 changed files with 2279 additions and 1206 deletions
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Debug Backend",
|
"name": "Debug Backend",
|
||||||
|
|
@ -38,6 +39,15 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"url": "http://localhost:3000/",
|
"url": "http://localhost:3000/",
|
||||||
"webRoot": "${workspaceRoot}/src/frontend"
|
"webRoot": "${workspaceRoot}/src/frontend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Python: Debug Tests",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"purpose": ["debug-test"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ to contributions, whether it be in the form of a new feature, improved infra, or
|
||||||
To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
|
To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
|
||||||
Please do not try to push directly to this repo unless you are a maintainer.
|
Please do not try to push directly to this repo unless you are a maintainer.
|
||||||
|
|
||||||
|
The branch structure is as follows:
|
||||||
|
|
||||||
|
- `main`: The stable version of Langflow
|
||||||
|
- `dev`: The development version of Langflow. This branch is used to test new features before they are merged into `main` and, as such, may be unstable.
|
||||||
|
|
||||||
## 🗺️Contributing Guidelines
|
## 🗺️Contributing Guidelines
|
||||||
|
|
||||||
## 🚩GitHub Issues
|
## 🚩GitHub Issues
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
||||||
|
|
@ -276,6 +275,8 @@ flow("Hey, have you heard of Langflow?")
|
||||||
|
|
||||||
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible.
|
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask questions, make suggestions and showcase your projects! 🦾
|
Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask questions, make suggestions and showcase your projects! 🦾
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "5003:5003"
|
- "7860:7860"
|
||||||
command: langflow --host 0.0.0.0
|
command: langflow --host 0.0.0.0
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ This guide takes you through the process of augmenting the "Basic Chat with Prom
|
||||||
|
|
||||||
8. Connect this loader to the `{context}` variable that we just added.
|
8. Connect this loader to the `{context}` variable that we just added.
|
||||||
|
|
||||||
9. In the "Web Page" field, enter "https://langflow.org/how-upload-examples".
|
9. In the "Web Page" field, enter "https://docs.langflow.org/how-upload-examples".
|
||||||
|
|
||||||
10. Now, click on "ConversationBufferMemory".
|
10. Now, click on "ConversationBufferMemory".
|
||||||
|
|
||||||
|
|
|
||||||
2
docs/static/CNAME
vendored
2
docs/static/CNAME
vendored
|
|
@ -1 +1 @@
|
||||||
langflow.org
|
docs.langflow.org
|
||||||
677
poetry.lock
generated
677
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "0.4.0"
|
version = "0.4.3"
|
||||||
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 = [
|
||||||
|
|
@ -19,7 +19,7 @@ readme = "README.md"
|
||||||
keywords = ["nlp", "langchain", "openai", "gpt", "gui"]
|
keywords = ["nlp", "langchain", "openai", "gpt", "gui"]
|
||||||
packages = [{ include = "langflow", from = "src/backend" }]
|
packages = [{ include = "langflow", from = "src/backend" }]
|
||||||
include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
|
include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
|
||||||
|
documentation = "https://docs.langflow.org"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
langflow = "langflow.__main__:main"
|
langflow = "langflow.__main__:main"
|
||||||
|
|
@ -33,7 +33,7 @@ google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
gunicorn = "^21.1.0"
|
gunicorn = "^21.1.0"
|
||||||
langchain = "^0.0.250"
|
langchain = "^0.0.256"
|
||||||
openai = "^0.27.8"
|
openai = "^0.27.8"
|
||||||
pandas = "^2.0.0"
|
pandas = "^2.0.0"
|
||||||
chromadb = "^0.3.21"
|
chromadb = "^0.3.21"
|
||||||
|
|
@ -63,7 +63,7 @@ python-multipart = "^0.0.6"
|
||||||
sqlmodel = "^0.0.8"
|
sqlmodel = "^0.0.8"
|
||||||
faiss-cpu = "^1.7.4"
|
faiss-cpu = "^1.7.4"
|
||||||
anthropic = "^0.3.0"
|
anthropic = "^0.3.0"
|
||||||
orjson = "^3.9.1"
|
orjson = "3.9.3"
|
||||||
multiprocess = "^0.70.14"
|
multiprocess = "^0.70.14"
|
||||||
cachetools = "^5.3.1"
|
cachetools = "^5.3.1"
|
||||||
types-cachetools = "^5.3.0.5"
|
types-cachetools = "^5.3.0.5"
|
||||||
|
|
@ -76,6 +76,8 @@ 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"
|
||||||
|
alembic = "^1.11.2"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from importlib import metadata
|
from importlib import metadata
|
||||||
from langflow.cache import cache_manager
|
|
||||||
|
# Deactivate cache manager for now
|
||||||
|
# from langflow.services.cache import cache_manager
|
||||||
from langflow.processing.process import load_flow_from_json
|
from langflow.processing.process import load_flow_from_json
|
||||||
from langflow.interface.custom.custom_component import CustomComponent
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import httpx
|
import httpx
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.utils.util import get_number_of_workers
|
from langflow.utils.util import get_number_of_workers
|
||||||
from multiprocess import Process # type: ignore
|
from multiprocess import Process # type: ignore
|
||||||
import platform
|
import platform
|
||||||
|
|
@ -13,7 +13,6 @@ from rich import box
|
||||||
from rich import print as rprint
|
from rich import print as rprint
|
||||||
import typer
|
import typer
|
||||||
from langflow.main import setup_app
|
from langflow.main import setup_app
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.utils.logger import configure, logger
|
from langflow.utils.logger import configure, logger
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
@ -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")
|
settings_manager = get_settings_manager()
|
||||||
|
|
||||||
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_manager.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_manager.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_manager.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_manager.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():
|
||||||
|
|
@ -131,10 +106,12 @@ def serve(
|
||||||
help="Path to the directory containing custom components.",
|
help="Path to the directory containing custom components.",
|
||||||
envvar="LANGFLOW_COMPONENTS_PATH",
|
envvar="LANGFLOW_COMPONENTS_PATH",
|
||||||
),
|
),
|
||||||
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
|
config: str = typer.Option(
|
||||||
|
Path(__file__).parent / "config.yaml", help="Path to the configuration file."
|
||||||
|
),
|
||||||
# .env file param
|
# .env file param
|
||||||
env_file: Path = typer.Option(
|
env_file: Path = typer.Option(
|
||||||
".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 +126,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.",
|
||||||
|
|
@ -169,6 +148,11 @@ def serve(
|
||||||
help="Remove API keys from the projects saved in the database.",
|
help="Remove API keys from the projects saved in the database.",
|
||||||
envvar="LANGFLOW_REMOVE_API_KEYS",
|
envvar="LANGFLOW_REMOVE_API_KEYS",
|
||||||
),
|
),
|
||||||
|
backend_only: bool = typer.Option(
|
||||||
|
False,
|
||||||
|
help="Run only the backend server without the frontend.",
|
||||||
|
envvar="LANGFLOW_BACKEND_ONLY",
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run the Langflow server.
|
Run the Langflow server.
|
||||||
|
|
@ -176,7 +160,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,14 +168,13 @@ 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,
|
||||||
)
|
)
|
||||||
# create path object if path is provided
|
# create path object if path is provided
|
||||||
static_files_dir: Optional[Path] = Path(path) if path else None
|
static_files_dir: Optional[Path] = Path(path) if path else None
|
||||||
app = setup_app(static_files_dir=static_files_dir)
|
app = setup_app(static_files_dir=static_files_dir, backend_only=backend_only)
|
||||||
# check if port is being used
|
# check if port is being used
|
||||||
if is_port_in_use(port, host):
|
if is_port_in_use(port, host):
|
||||||
port = get_free_port(port)
|
port = get_free_port(port)
|
||||||
|
|
@ -204,6 +186,10 @@ def serve(
|
||||||
"timeout": timeout,
|
"timeout": timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Define an env variable to know if we are just testing the server
|
||||||
|
if "pytest" in sys.modules:
|
||||||
|
return
|
||||||
|
|
||||||
if platform.system() in ["Windows"]:
|
if platform.system() in ["Windows"]:
|
||||||
# Run using uvicorn on MacOS and Windows
|
# Run using uvicorn on MacOS and Windows
|
||||||
# Windows doesn't support gunicorn
|
# Windows doesn't support gunicorn
|
||||||
|
|
|
||||||
113
src/backend/langflow/alembic.ini
Normal file
113
src/backend/langflow/alembic.ini
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# path to migration scripts
|
||||||
|
script_location = alembic
|
||||||
|
|
||||||
|
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||||
|
# Uncomment the line below if you want the files to be prepended with date and time
|
||||||
|
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||||
|
# for all available tokens
|
||||||
|
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# sys.path path, will be prepended to sys.path if present.
|
||||||
|
# defaults to the current working directory.
|
||||||
|
prepend_sys_path = .
|
||||||
|
|
||||||
|
# timezone to use when rendering the date within the migration file
|
||||||
|
# as well as the filename.
|
||||||
|
# If specified, requires the python-dateutil library that can be
|
||||||
|
# installed by adding `alembic[tz]` to the pip requirements
|
||||||
|
# string value is passed to dateutil.tz.gettz()
|
||||||
|
# leave blank for localtime
|
||||||
|
# timezone =
|
||||||
|
|
||||||
|
# max length of characters to apply to the
|
||||||
|
# "slug" field
|
||||||
|
# truncate_slug_length = 40
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
# set to 'true' to allow .pyc and .pyo files without
|
||||||
|
# a source .py file to be detected as revisions in the
|
||||||
|
# versions/ directory
|
||||||
|
# sourceless = false
|
||||||
|
|
||||||
|
# version location specification; This defaults
|
||||||
|
# to alembic/versions. When using multiple version
|
||||||
|
# directories, initial revisions must be specified with --version-path.
|
||||||
|
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||||
|
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
||||||
|
|
||||||
|
# version path separator; As mentioned above, this is the character used to split
|
||||||
|
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||||
|
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||||
|
# Valid values for version_path_separator are:
|
||||||
|
#
|
||||||
|
# version_path_separator = :
|
||||||
|
# version_path_separator = ;
|
||||||
|
# version_path_separator = space
|
||||||
|
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||||
|
|
||||||
|
# set to 'true' to search source files recursively
|
||||||
|
# in each "version_locations" directory
|
||||||
|
# new in Alembic version 1.10
|
||||||
|
# recursive_version_locations = false
|
||||||
|
|
||||||
|
# the output encoding used when revision files
|
||||||
|
# are written from script.py.mako
|
||||||
|
# output_encoding = utf-8
|
||||||
|
|
||||||
|
# This is the path to the db in the root of the project.
|
||||||
|
# When the user runs the Langflow the database url will
|
||||||
|
# be set dinamically.
|
||||||
|
sqlalchemy.url = sqlite:///../../../langflow.db
|
||||||
|
|
||||||
|
|
||||||
|
[post_write_hooks]
|
||||||
|
# post_write_hooks defines scripts or Python functions that are run
|
||||||
|
# on newly generated revision scripts. See the documentation for further
|
||||||
|
# detail and examples
|
||||||
|
|
||||||
|
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||||
|
# hooks = black
|
||||||
|
# black.type = console_scripts
|
||||||
|
# black.entrypoint = black
|
||||||
|
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
||||||
1
src/backend/langflow/alembic/README
Normal file
1
src/backend/langflow/alembic/README
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Generic single-database configuration.
|
||||||
78
src/backend/langflow/alembic/env.py
Normal file
78
src/backend/langflow/alembic/env.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
from langflow.services.database.manager import SQLModel
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
if config.config_file_name is not None:
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
target_metadata = SQLModel.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline() -> None:
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online() -> None:
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
connectable = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section, {}),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
27
src/backend/langflow/alembic/script.py.mako
Normal file
27
src/backend/langflow/alembic/script.py.mako
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = ${repr(up_revision)}
|
||||||
|
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||||
|
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
"""Remove FlowStyles table
|
||||||
|
|
||||||
|
Revision ID: 0a534bdfd84b
|
||||||
|
Revises: 4814b6f4abfd
|
||||||
|
Create Date: 2023-08-07 14:09:06.844104
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "0a534bdfd84b"
|
||||||
|
down_revision: Union[str, None] = "4814b6f4abfd"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("flowstyle")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"flowstyle",
|
||||||
|
sa.Column("color", sa.VARCHAR(), nullable=False),
|
||||||
|
sa.Column("emoji", sa.VARCHAR(), nullable=False),
|
||||||
|
sa.Column("flow_id", sa.CHAR(length=32), nullable=True),
|
||||||
|
sa.Column("id", sa.CHAR(length=32), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["flow_id"],
|
||||||
|
["flow.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""Add Flow table
|
||||||
|
|
||||||
|
Revision ID: 4814b6f4abfd
|
||||||
|
Revises:
|
||||||
|
Create Date: 2023-08-05 17:47:42.879824
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "4814b6f4abfd"
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
|
||||||
|
# This suppress is used to not break the migration if the table already exists.
|
||||||
|
with contextlib.suppress(sa.exc.OperationalError):
|
||||||
|
op.create_table(
|
||||||
|
"flow",
|
||||||
|
sa.Column("data", sa.JSON(), nullable=True),
|
||||||
|
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_flow_description"), "flow", ["description"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_flow_name"), "flow", ["name"], unique=False)
|
||||||
|
with contextlib.suppress(sa.exc.OperationalError):
|
||||||
|
op.create_table(
|
||||||
|
"flowstyle",
|
||||||
|
sa.Column("color", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column("emoji", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column("flow_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
|
||||||
|
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["flow_id"],
|
||||||
|
["flow.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("flowstyle")
|
||||||
|
op.drop_index(op.f("ix_flow_name"), table_name="flow")
|
||||||
|
op.drop_index(op.f("ix_flow_description"), table_name="flow")
|
||||||
|
op.drop_table("flow")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -5,7 +5,6 @@ from langflow.api.v1 import (
|
||||||
endpoints_router,
|
endpoints_router,
|
||||||
validate_router,
|
validate_router,
|
||||||
flows_router,
|
flows_router,
|
||||||
flow_styles_router,
|
|
||||||
component_router,
|
component_router,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -17,4 +16,3 @@ router.include_router(endpoints_router)
|
||||||
router.include_router(validate_router)
|
router.include_router(validate_router)
|
||||||
router.include_router(component_router)
|
router.include_router(component_router)
|
||||||
router.include_router(flows_router)
|
router.include_router(flows_router)
|
||||||
router.include_router(flow_styles_router)
|
|
||||||
|
|
|
||||||
|
|
@ -66,3 +66,30 @@ def merge_nested_dicts(dict1, dict2):
|
||||||
else:
|
else:
|
||||||
dict1[key] = value
|
dict1[key] = value
|
||||||
return dict1
|
return dict1
|
||||||
|
|
||||||
|
|
||||||
|
def merge_nested_dicts_with_renaming(dict1, dict2):
|
||||||
|
for key, value in dict2.items():
|
||||||
|
if (
|
||||||
|
key in dict1
|
||||||
|
and isinstance(value, dict)
|
||||||
|
and isinstance(dict1.get(key), dict)
|
||||||
|
):
|
||||||
|
for sub_key, sub_value in value.items():
|
||||||
|
if sub_key in dict1[key]:
|
||||||
|
new_key = get_new_key(dict1[key], sub_key)
|
||||||
|
dict1[key][new_key] = sub_value
|
||||||
|
else:
|
||||||
|
dict1[key][sub_key] = sub_value
|
||||||
|
else:
|
||||||
|
dict1[key] = value
|
||||||
|
return dict1
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_key(dictionary, original_key):
|
||||||
|
counter = 1
|
||||||
|
new_key = original_key + " (" + str(counter) + ")"
|
||||||
|
while new_key in dictionary:
|
||||||
|
counter += 1
|
||||||
|
new_key = original_key + " (" + str(counter) + ")"
|
||||||
|
return new_key
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ from langflow.api.v1.endpoints import router as endpoints_router
|
||||||
from langflow.api.v1.validate import router as validate_router
|
from langflow.api.v1.validate import router as validate_router
|
||||||
from langflow.api.v1.chat import router as chat_router
|
from langflow.api.v1.chat import router as chat_router
|
||||||
from langflow.api.v1.flows import router as flows_router
|
from langflow.api.v1.flows import router as flows_router
|
||||||
from langflow.api.v1.flow_styles import router as flow_styles_router
|
|
||||||
from langflow.api.v1.components import router as component_router
|
from langflow.api.v1.components import router as component_router
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -11,5 +10,4 @@ __all__ = [
|
||||||
"component_router",
|
"component_router",
|
||||||
"validate_router",
|
"validate_router",
|
||||||
"flows_router",
|
"flows_router",
|
||||||
"flow_styles_router",
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ from fastapi.responses import StreamingResponse
|
||||||
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.chat.manager import ChatManager
|
from langflow.services import service_manager, ServiceType
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from cachetools import LRUCache
|
from cachetools import LRUCache
|
||||||
|
|
||||||
router = APIRouter(tags=["Chat"])
|
router = APIRouter(tags=["Chat"])
|
||||||
chat_manager = ChatManager()
|
|
||||||
flow_data_store: LRUCache = LRUCache(maxsize=10)
|
flow_data_store: LRUCache = LRUCache(maxsize=10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,6 +17,7 @@ flow_data_store: LRUCache = LRUCache(maxsize=10)
|
||||||
async def chat(client_id: str, websocket: WebSocket):
|
async def chat(client_id: str, websocket: WebSocket):
|
||||||
"""Websocket endpoint for chat."""
|
"""Websocket endpoint for chat."""
|
||||||
try:
|
try:
|
||||||
|
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
|
||||||
if client_id in chat_manager.in_memory_cache:
|
if client_id in chat_manager.in_memory_cache:
|
||||||
await chat_manager.handle_websocket(client_id, websocket)
|
await chat_manager.handle_websocket(client_id, websocket)
|
||||||
else:
|
else:
|
||||||
|
|
@ -45,6 +46,7 @@ async def init_build(graph_data: dict, flow_id: str):
|
||||||
return InitResponse(flowId=flow_id)
|
return InitResponse(flowId=flow_id)
|
||||||
|
|
||||||
# Delete from cache if already exists
|
# Delete from cache if already exists
|
||||||
|
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
|
||||||
if flow_id in chat_manager.in_memory_cache:
|
if flow_id in chat_manager.in_memory_cache:
|
||||||
with chat_manager.in_memory_cache._lock:
|
with chat_manager.in_memory_cache._lock:
|
||||||
chat_manager.in_memory_cache.delete(flow_id)
|
chat_manager.in_memory_cache.delete(flow_id)
|
||||||
|
|
@ -125,9 +127,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
|
||||||
|
|
@ -156,12 +157,12 @@ async def stream_build(flow_id: str):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
input_keys_response = {
|
input_keys_response = {
|
||||||
"input_keys": {},
|
"input_keys": None,
|
||||||
"memory_keys": [],
|
"memory_keys": [],
|
||||||
"handle_keys": [],
|
"handle_keys": [],
|
||||||
}
|
}
|
||||||
yield str(StreamData(event="message", data=input_keys_response))
|
yield str(StreamData(event="message", data=input_keys_response))
|
||||||
|
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
|
||||||
chat_manager.set_cache(flow_id, langchain_object)
|
chat_manager.set_cache(flow_id, langchain_object)
|
||||||
# We need to reset the chat history
|
# We need to reset the chat history
|
||||||
chat_manager.chat_history.empty_history(flow_id)
|
chat_manager.chat_history.empty_history(flow_id)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow.database.models.component import Component, ComponentModel
|
from langflow.services.database.models.component import Component, ComponentModel
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, Optional
|
from typing import Annotated, Optional
|
||||||
|
|
||||||
from langflow.cache.utils import save_uploaded_file
|
from langflow.services.cache.utils import save_uploaded_file
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.services.database.models.flow import Flow
|
||||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.settings import settings
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body
|
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,
|
||||||
|
|
@ -21,7 +17,7 @@ from langflow.api.v1.schemas import (
|
||||||
CustomComponentCode,
|
CustomComponentCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.api.utils import merge_nested_dicts
|
from langflow.api.utils import merge_nested_dicts_with_renaming
|
||||||
|
|
||||||
from langflow.interface.types import (
|
from langflow.interface.types import (
|
||||||
build_langchain_types_dict,
|
build_langchain_types_dict,
|
||||||
|
|
@ -29,7 +25,7 @@ from langflow.interface.types import (
|
||||||
build_langchain_custom_component_list_from_path,
|
build_langchain_custom_component_list_from_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
# build router
|
# build router
|
||||||
|
|
@ -38,48 +34,38 @@ 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:
|
settings_manager = get_settings_manager()
|
||||||
|
if settings_manager.settings.COMPONENTS_PATH:
|
||||||
|
logger.info(
|
||||||
|
f"Building custom components from {settings_manager.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_manager.settings.COMPONENTS_PATH
|
||||||
]
|
]
|
||||||
|
logger.info(f"Loading {len(custom_component_dicts)} category(ies)")
|
||||||
for custom_component_dict in custom_component_dicts:
|
for custom_component_dict in custom_component_dicts:
|
||||||
custom_components_from_file = merge_nested_dicts(
|
# custom_component_dict is a dict of dicts
|
||||||
|
if not custom_component_dict:
|
||||||
|
continue
|
||||||
|
category = list(custom_component_dict.keys())[0]
|
||||||
|
logger.info(
|
||||||
|
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
||||||
|
)
|
||||||
|
logger.debug(custom_component_dict)
|
||||||
|
custom_components_from_file = merge_nested_dicts_with_renaming(
|
||||||
custom_components_from_file, custom_component_dict
|
custom_components_from_file, custom_component_dict
|
||||||
)
|
)
|
||||||
return merge_nested_dicts(native_components, custom_components_from_file)
|
|
||||||
|
|
||||||
|
return merge_nested_dicts_with_renaming(
|
||||||
@router.get("/load_custom_component_from_path")
|
native_components, custom_components_from_file
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
from uuid import UUID
|
|
||||||
from langflow.database.models.flow_style import (
|
|
||||||
FlowStyle,
|
|
||||||
FlowStyleCreate,
|
|
||||||
FlowStyleRead,
|
|
||||||
FlowStyleUpdate,
|
|
||||||
)
|
|
||||||
from langflow.database.base import get_session
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
|
||||||
|
|
||||||
|
|
||||||
# build router
|
|
||||||
router = APIRouter(prefix="/flow_styles", tags=["FlowStyles"])
|
|
||||||
|
|
||||||
# FlowStyleCreate:
|
|
||||||
# class FlowStyleBase(SQLModel):
|
|
||||||
# color: str = Field(index=True)
|
|
||||||
# emoji: str = Field(index=False)
|
|
||||||
# flow_id: UUID = Field(default=None, foreign_key="flow.id")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=FlowStyleRead)
|
|
||||||
def create_flow_style(
|
|
||||||
*, session: Session = Depends(get_session), flow_style: FlowStyleCreate
|
|
||||||
):
|
|
||||||
"""Create a new flow_style."""
|
|
||||||
db_flow_style = FlowStyle.from_orm(flow_style)
|
|
||||||
session.add(db_flow_style)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_flow_style)
|
|
||||||
return db_flow_style
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[FlowStyleRead])
|
|
||||||
def read_flow_styles(*, session: Session = Depends(get_session)):
|
|
||||||
"""Read all flows."""
|
|
||||||
try:
|
|
||||||
flows = session.exec(select(FlowStyle)).all()
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
||||||
return flows
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{flow_styles_id}", response_model=FlowStyleRead)
|
|
||||||
def read_flow_style(*, session: Session = Depends(get_session), flow_styles_id: UUID):
|
|
||||||
"""Read a flow_style."""
|
|
||||||
if flow_style := session.get(FlowStyle, flow_styles_id):
|
|
||||||
return flow_style
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=404, detail="FlowStyle not found")
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{flow_style_id}", response_model=FlowStyleRead)
|
|
||||||
def update_flow_style(
|
|
||||||
*,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
flow_style_id: UUID,
|
|
||||||
flow_style: FlowStyleUpdate,
|
|
||||||
):
|
|
||||||
"""Update a flow_style."""
|
|
||||||
db_flow_style = session.get(FlowStyle, flow_style_id)
|
|
||||||
if not db_flow_style:
|
|
||||||
raise HTTPException(status_code=404, detail="FlowStyle not found")
|
|
||||||
flow_data = flow_style.dict(exclude_unset=True)
|
|
||||||
for key, value in flow_data.items():
|
|
||||||
if hasattr(db_flow_style, key) and value is not None:
|
|
||||||
setattr(db_flow_style, key, value)
|
|
||||||
session.add(db_flow_style)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_flow_style)
|
|
||||||
return db_flow_style
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{flow_id}")
|
|
||||||
def delete_flow_style(*, session: Session = Depends(get_session), flow_id: UUID):
|
|
||||||
"""Delete a flow_style."""
|
|
||||||
flow_style = session.get(FlowStyle, flow_id)
|
|
||||||
if not flow_style:
|
|
||||||
raise HTTPException(status_code=404, detail="FlowStyle not found")
|
|
||||||
session.delete(flow_style)
|
|
||||||
session.commit()
|
|
||||||
return {"message": "FlowStyle deleted successfully"}
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.api.utils import remove_api_keys
|
from langflow.api.utils import remove_api_keys
|
||||||
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
||||||
from langflow.database.models.flow import (
|
from langflow.services.database.models.flow import (
|
||||||
Flow,
|
Flow,
|
||||||
FlowCreate,
|
FlowCreate,
|
||||||
FlowRead,
|
FlowRead,
|
||||||
FlowReadWithStyle,
|
|
||||||
FlowUpdate,
|
FlowUpdate,
|
||||||
)
|
)
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
|
@ -32,7 +31,7 @@ def create_flow(*, session: Session = Depends(get_session), flow: FlowCreate):
|
||||||
return db_flow
|
return db_flow
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[FlowReadWithStyle], status_code=200)
|
@router.get("/", response_model=list[FlowRead], status_code=200)
|
||||||
def read_flows(*, session: Session = Depends(get_session)):
|
def read_flows(*, session: Session = Depends(get_session)):
|
||||||
"""Read all flows."""
|
"""Read all flows."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -42,7 +41,7 @@ def read_flows(*, session: Session = Depends(get_session)):
|
||||||
return [jsonable_encoder(flow) for flow in flows]
|
return [jsonable_encoder(flow) for flow in flows]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{flow_id}", response_model=FlowReadWithStyle, status_code=200)
|
@router.get("/{flow_id}", response_model=FlowRead, status_code=200)
|
||||||
def read_flow(*, session: Session = Depends(get_session), flow_id: UUID):
|
def read_flow(*, session: Session = Depends(get_session), flow_id: UUID):
|
||||||
"""Read a flow."""
|
"""Read a flow."""
|
||||||
if flow := session.get(Flow, flow_id):
|
if flow := session.get(Flow, flow_id):
|
||||||
|
|
@ -61,7 +60,8 @@ 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:
|
settings_manager = get_settings_manager()
|
||||||
|
if settings_manager.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)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
from langflow.database.models.flow import FlowCreate, FlowRead
|
from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from fastapi import Depends, HTTPException, status
|
|
||||||
from passlib.context import CryptContext
|
|
||||||
from jose import JWTError, jwt
|
from jose import JWTError, jwt
|
||||||
from datetime import datetime, timedelta, timezone
|
from sqlalchemy.orm import Session
|
||||||
|
from passlib.context import CryptContext
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from langflow.database.models.token import TokenData
|
from langflow.database.models.token import TokenData
|
||||||
from langflow.database.models.user import get_user, User
|
from langflow.database.models.user import get_user, User
|
||||||
from sqlalchemy.orm import Session
|
from langflow.services.utils import get_session
|
||||||
from langflow.database.base import get_session
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: Move to env - Test propose!!!!!
|
# TODO: Move to env - Test propose!!!!!
|
||||||
|
|
|
||||||
7
src/backend/langflow/cache/__init__.py
vendored
7
src/backend/langflow/cache/__init__.py
vendored
|
|
@ -1,7 +0,0 @@
|
||||||
from langflow.cache.manager import cache_manager
|
|
||||||
from langflow.cache.flow import InMemoryCache
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"cache_manager",
|
|
||||||
"InMemoryCache",
|
|
||||||
]
|
|
||||||
4
src/backend/langflow/components/__init__.py
Normal file
4
src/backend/langflow/components/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["CustomComponent"]
|
||||||
33
src/backend/langflow/components/chains/prompt_runner.py
Normal file
33
src/backend/langflow/components/chains/prompt_runner.py
Normal 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))
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
from contextlib import contextmanager
|
|
||||||
from langflow.settings import settings
|
|
||||||
from sqlmodel import SQLModel, Session, create_engine
|
|
||||||
from langflow.utils.logger import logger
|
|
||||||
|
|
||||||
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")
|
|
||||||
engine = create_engine(settings.database_url, connect_args=connect_args)
|
|
||||||
|
|
||||||
|
|
||||||
def create_db_and_tables():
|
|
||||||
logger.debug("Creating database and tables")
|
|
||||||
try:
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"Error creating database and tables: {exc}")
|
|
||||||
raise RuntimeError("Error creating database and tables") from exc
|
|
||||||
# Now check if the table Flow exists, if not, something went wrong
|
|
||||||
# and we need to create the tables again.
|
|
||||||
from sqlalchemy import inspect
|
|
||||||
|
|
||||||
inspector = inspect(engine)
|
|
||||||
if "flow" not in inspector.get_table_names():
|
|
||||||
logger.error("Something went wrong creating the database and tables.")
|
|
||||||
logger.error("Please check your database settings.")
|
|
||||||
|
|
||||||
raise RuntimeError("Something went wrong creating the database and tables.")
|
|
||||||
else:
|
|
||||||
logger.debug("Database and tables created successfully")
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def session_getter():
|
|
||||||
try:
|
|
||||||
session = Session(engine)
|
|
||||||
yield session
|
|
||||||
except Exception as e:
|
|
||||||
print("Session rollback because of exception:", e)
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
|
|
||||||
def get_session():
|
|
||||||
with session_getter() as session:
|
|
||||||
yield session
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# Path: src/backend/langflow/database/models/flowstyle.py
|
|
||||||
|
|
||||||
from langflow.database.models.base import SQLModelSerializable
|
|
||||||
from sqlmodel import Field, Relationship
|
|
||||||
from uuid import UUID, uuid4
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from langflow.database.models.flow import Flow
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleBase(SQLModelSerializable):
|
|
||||||
color: str
|
|
||||||
emoji: str
|
|
||||||
flow_id: UUID = Field(default=None, foreign_key="flow.id")
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyle(FlowStyleBase, table=True):
|
|
||||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
|
||||||
flow: "Flow" = Relationship(back_populates="style")
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleUpdate(SQLModelSerializable):
|
|
||||||
color: Optional[str] = None
|
|
||||||
emoji: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleCreate(FlowStyleBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleRead(FlowStyleBase):
|
|
||||||
id: UUID
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from langflow.database.models.base import SQLModelSerializable, SQLModel
|
from langflow.services.database.models.base import (
|
||||||
|
SQLModelSerializable,
|
||||||
|
SQLModel
|
||||||
|
)
|
||||||
from sqlmodel import Field
|
from sqlmodel import Field
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Dict, Generator, List, Type, Union
|
from typing import Dict, Generator, List, Type, Union
|
||||||
|
|
||||||
from langflow.graph.edge.base import Edge
|
from langflow.graph.edge.base import Edge
|
||||||
from langflow.graph.graph.constants import VERTEX_TYPE_MAP
|
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
from langflow.graph.vertex.types import (
|
from langflow.graph.vertex.types import (
|
||||||
FileToolVertex,
|
FileToolVertex,
|
||||||
|
|
@ -187,10 +187,12 @@ class Graph:
|
||||||
"""Returns the node class based on the node type."""
|
"""Returns the node class based on the node type."""
|
||||||
if node_type in FILE_TOOLS:
|
if node_type in FILE_TOOLS:
|
||||||
return FileToolVertex
|
return FileToolVertex
|
||||||
if node_type in VERTEX_TYPE_MAP:
|
if node_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
|
||||||
return VERTEX_TYPE_MAP[node_type]
|
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_type]
|
||||||
return (
|
return (
|
||||||
VERTEX_TYPE_MAP[node_lc_type] if node_lc_type in VERTEX_TYPE_MAP else Vertex
|
lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_lc_type]
|
||||||
|
if node_lc_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP
|
||||||
|
else Vertex
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_vertices(self) -> List[Vertex]:
|
def _build_vertices(self) -> List[Vertex]:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from langflow.graph.vertex.base import Vertex
|
|
||||||
from langflow.graph.vertex import types
|
from langflow.graph.vertex import types
|
||||||
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
|
||||||
|
|
@ -15,23 +14,45 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from langflow.interface.output_parsers.base import output_parser_creator
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
from langflow.interface.retrievers.base import retriever_creator
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
from langflow.interface.custom.base import custom_component_creator
|
from langflow.interface.custom.base import custom_component_creator
|
||||||
from typing import Dict, Type
|
from langflow.utils.lazy_load import LazyLoadDictBase
|
||||||
|
|
||||||
|
|
||||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
class VertexTypesDict(LazyLoadDictBase):
|
||||||
**{t: types.PromptVertex for t in prompt_creator.to_list()},
|
def __init__(self):
|
||||||
**{t: types.AgentVertex for t in agent_creator.to_list()},
|
self._all_types_dict = None
|
||||||
**{t: types.ChainVertex for t in chain_creator.to_list()},
|
|
||||||
**{t: types.ToolVertex for t in tool_creator.to_list()},
|
@property
|
||||||
**{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
|
def VERTEX_TYPE_MAP(self):
|
||||||
**{t: types.WrapperVertex for t in wrapper_creator.to_list()},
|
return self.all_types_dict
|
||||||
**{t: types.LLMVertex for t in llm_creator.to_list()},
|
|
||||||
**{t: types.MemoryVertex for t in memory_creator.to_list()},
|
def _build_dict(self):
|
||||||
**{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
|
langchain_types_dict = self.get_type_dict()
|
||||||
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
|
return {
|
||||||
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
**langchain_types_dict,
|
||||||
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
"Custom": ["Custom Tool", "Python Function"],
|
||||||
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
}
|
||||||
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
|
|
||||||
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
def get_type_dict(self):
|
||||||
}
|
return {
|
||||||
|
**{t: types.PromptVertex for t in prompt_creator.to_list()},
|
||||||
|
**{t: types.AgentVertex for t in agent_creator.to_list()},
|
||||||
|
**{t: types.ChainVertex for t in chain_creator.to_list()},
|
||||||
|
**{t: types.ToolVertex for t in tool_creator.to_list()},
|
||||||
|
**{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
|
||||||
|
**{t: types.WrapperVertex for t in wrapper_creator.to_list()},
|
||||||
|
**{t: types.LLMVertex for t in llm_creator.to_list()},
|
||||||
|
**{t: types.MemoryVertex for t in memory_creator.to_list()},
|
||||||
|
**{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
|
||||||
|
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
|
||||||
|
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||||
|
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||||
|
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
||||||
|
**{
|
||||||
|
t: types.CustomComponentVertex
|
||||||
|
for t in custom_component_creator.to_list()
|
||||||
|
},
|
||||||
|
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lazy_load_vertex_dict = VertexTypesDict()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
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 lazy_load_dict
|
||||||
from langflow.utils.constants import DIRECT_TYPES
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import sync_to_async
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
@ -61,7 +62,7 @@ class Vertex:
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.base_type is None:
|
if self.base_type is None:
|
||||||
for base_type, value in ALL_TYPES_DICT.items():
|
for base_type, value in lazy_load_dict.ALL_TYPES_DICT.items():
|
||||||
if self.vertex_type in value:
|
if self.vertex_type in value:
|
||||||
self.base_type = base_type
|
self.base_type = base_type
|
||||||
break
|
break
|
||||||
|
|
@ -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 😵💫"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from langchain.agents import types
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.agents import AgentFrontendNode
|
from langflow.template.frontend_node.agents import AgentFrontendNode
|
||||||
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
|
||||||
|
|
@ -53,13 +54,17 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
# Now this is a generator
|
# Now this is a generator
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
for _, agent in self.type_to_loader_dict.items():
|
for _, agent in self.type_to_loader_dict.items():
|
||||||
agent_name = (
|
agent_name = (
|
||||||
agent.function_name()
|
agent.function_name()
|
||||||
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_manager.settings.AGENTS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
|
):
|
||||||
names.append(agent_name)
|
names.append(agent_name)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, List, Optional, Type, Union
|
from typing import Any, Dict, List, Optional, Type, Union
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.agents import AgentExecutor
|
from langchain.agents import AgentExecutor
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.template.base import Template
|
from langflow.template.template.base import Template
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.settings import settings
|
|
||||||
|
|
||||||
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
||||||
|
|
||||||
|
|
@ -26,9 +27,12 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
@property
|
@property
|
||||||
def docs_map(self) -> Dict[str, str]:
|
def docs_map(self) -> Dict[str, str]:
|
||||||
"""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."""
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.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()
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ from typing import Any, Dict, List, Optional, Type
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.chains import ChainFrontendNode
|
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
|
||||||
|
|
||||||
|
|
@ -29,18 +31,22 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict: dict[str, Any] = {
|
self.type_dict: dict[str, Any] = {
|
||||||
chain_name: import_class(f"langchain.chains.{chain_name}")
|
chain_name: import_class(f"langchain.chains.{chain_name}")
|
||||||
for chain_name in chains.__all__
|
for chain_name in chains.__all__
|
||||||
}
|
}
|
||||||
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
||||||
|
|
||||||
|
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_manager.settings.CHAINS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ from fastapi import HTTPException
|
||||||
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||||
from langflow.interface.custom.component import Component
|
from langflow.interface.custom.component import Component
|
||||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||||
|
from langflow.services.utils import get_db_manager
|
||||||
|
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
|
|
||||||
from langflow.database.base import session_getter
|
from langflow.services.database.utils import session_getter
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.services.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 +26,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):
|
||||||
|
|
@ -154,7 +160,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
from langflow.processing.process import build_sorted_vertices_with_caching
|
from langflow.processing.process import build_sorted_vertices_with_caching
|
||||||
from langflow.processing.process import process_tweaks
|
from langflow.processing.process import process_tweaks
|
||||||
|
|
||||||
with session_getter() as session:
|
db_manager = get_db_manager()
|
||||||
|
with session_getter(db_manager) as session:
|
||||||
graph_data = flow.data if (flow := session.get(Flow, flow_id)) else None
|
graph_data = flow.data if (flow := session.get(Flow, flow_id)) else None
|
||||||
if not graph_data:
|
if not graph_data:
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
|
|
@ -164,7 +171,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
|
|
||||||
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
||||||
get_session = get_session or session_getter
|
get_session = get_session or session_getter
|
||||||
with get_session() as session:
|
db_manager = get_db_manager()
|
||||||
|
with get_session(db_manager) as session:
|
||||||
flows = session.query(Flow).all()
|
flows = session.query(Flow).all()
|
||||||
return flows
|
return flows
|
||||||
|
|
||||||
|
|
@ -177,8 +185,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
get_session: Optional[Callable] = None,
|
get_session: Optional[Callable] = None,
|
||||||
) -> Flow:
|
) -> Flow:
|
||||||
get_session = get_session or session_getter
|
get_session = get_session or session_getter
|
||||||
|
db_manager = get_db_manager()
|
||||||
with get_session() as session:
|
with get_session(db_manager) as session:
|
||||||
if flow_id:
|
if flow_id:
|
||||||
flow = session.query(Flow).get(flow_id)
|
flow = session.query(Flow).get(flow_id)
|
||||||
elif flow_name:
|
elif flow_name:
|
||||||
|
|
@ -190,5 +198,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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
|
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
|
||||||
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
|
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
@ -30,10 +31,12 @@ class DocumentLoaderCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.DOCUMENTLOADERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
|
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -32,10 +33,12 @@ class EmbeddingCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.EMBEDDINGS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,26 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
|
|
||||||
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
"""Initialize a ChromaDB object from the params"""
|
"""Initialize a ChromaDB object from the params"""
|
||||||
|
if ( # type: ignore
|
||||||
|
"chroma_server_host" in params or "chroma_server_http_port" in params
|
||||||
|
):
|
||||||
|
import chromadb # type: ignore
|
||||||
|
|
||||||
|
settings_params = {
|
||||||
|
key: params[key]
|
||||||
|
for key, value_ in params.items()
|
||||||
|
if key.startswith("chroma_server_") and value_
|
||||||
|
}
|
||||||
|
chroma_settings = chromadb.config.Settings(**settings_params)
|
||||||
|
params["client_settings"] = chroma_settings
|
||||||
|
else:
|
||||||
|
# remove all chroma_server_ keys from params
|
||||||
|
params = {
|
||||||
|
key: value
|
||||||
|
for key, value in params.items()
|
||||||
|
if not key.startswith("chroma_server_")
|
||||||
|
}
|
||||||
|
|
||||||
persist = params.pop("persist", False)
|
persist = params.pop("persist", False)
|
||||||
if not docs_in_params(params):
|
if not docs_in_params(params):
|
||||||
params.pop("documents", None)
|
params.pop("documents", None)
|
||||||
|
|
|
||||||
|
|
@ -14,34 +14,43 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from langflow.interface.output_parsers.base import output_parser_creator
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
from langflow.interface.retrievers.base import retriever_creator
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
from langflow.interface.custom.base import custom_component_creator
|
from langflow.interface.custom.base import custom_component_creator
|
||||||
|
from langflow.utils.lazy_load import LazyLoadDictBase
|
||||||
|
|
||||||
|
|
||||||
def get_type_dict():
|
class AllTypesDict(LazyLoadDictBase):
|
||||||
return {
|
def __init__(self):
|
||||||
"agents": agent_creator.to_list(),
|
self._all_types_dict = None
|
||||||
"prompts": prompt_creator.to_list(),
|
|
||||||
"llms": llm_creator.to_list(),
|
@property
|
||||||
"tools": tool_creator.to_list(),
|
def ALL_TYPES_DICT(self):
|
||||||
"chains": chain_creator.to_list(),
|
return self.all_types_dict
|
||||||
"memory": memory_creator.to_list(),
|
|
||||||
"toolkits": toolkits_creator.to_list(),
|
def _build_dict(self):
|
||||||
"wrappers": wrapper_creator.to_list(),
|
langchain_types_dict = self.get_type_dict()
|
||||||
"documentLoaders": documentloader_creator.to_list(),
|
return {
|
||||||
"vectorStore": vectorstore_creator.to_list(),
|
**langchain_types_dict,
|
||||||
"embeddings": embedding_creator.to_list(),
|
"Custom": ["Custom Tool", "Python Function"],
|
||||||
"textSplitters": textsplitter_creator.to_list(),
|
}
|
||||||
"utilities": utility_creator.to_list(),
|
|
||||||
"outputParsers": output_parser_creator.to_list(),
|
def get_type_dict(self):
|
||||||
"retrievers": retriever_creator.to_list(),
|
return {
|
||||||
"custom_components": custom_component_creator.to_list(),
|
"agents": agent_creator.to_list(),
|
||||||
}
|
"prompts": prompt_creator.to_list(),
|
||||||
|
"llms": llm_creator.to_list(),
|
||||||
|
"tools": tool_creator.to_list(),
|
||||||
|
"chains": chain_creator.to_list(),
|
||||||
|
"memory": memory_creator.to_list(),
|
||||||
|
"toolkits": toolkits_creator.to_list(),
|
||||||
|
"wrappers": wrapper_creator.to_list(),
|
||||||
|
"documentLoaders": documentloader_creator.to_list(),
|
||||||
|
"vectorStore": vectorstore_creator.to_list(),
|
||||||
|
"embeddings": embedding_creator.to_list(),
|
||||||
|
"textSplitters": textsplitter_creator.to_list(),
|
||||||
|
"utilities": utility_creator.to_list(),
|
||||||
|
"outputParsers": output_parser_creator.to_list(),
|
||||||
|
"retrievers": retriever_creator.to_list(),
|
||||||
|
"custom_components": custom_component_creator.to_list(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
LANGCHAIN_TYPES_DICT = get_type_dict()
|
lazy_load_dict = AllTypesDict()
|
||||||
|
|
||||||
# Now we'll build a dict with Langchain types and ours
|
|
||||||
|
|
||||||
ALL_TYPES_DICT = {
|
|
||||||
**LANGCHAIN_TYPES_DICT,
|
|
||||||
"Custom": ["Custom Tool", "Python Function"],
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.llms import LLMFrontendNode
|
from langflow.template.frontend_node.llms import LLMFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
@ -33,10 +34,12 @@ class LLMCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.LLMS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.frontend_node.memories import MemoryFrontendNode
|
from langflow.template.frontend_node.memories import MemoryFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -48,10 +49,12 @@ class MemoryCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.MEMORIES
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain import output_parsers
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
||||||
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
|
||||||
|
|
@ -23,6 +24,7 @@ class OutputParserCreator(LangChainTypeCreator):
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
output_parser_name: import_class(
|
output_parser_name: import_class(
|
||||||
f"langchain.output_parsers.{output_parser_name}"
|
f"langchain.output_parsers.{output_parser_name}"
|
||||||
|
|
@ -33,7 +35,8 @@ 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_manager.settings.OUTPUT_PARSERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from langchain import prompts
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
@ -20,6 +21,7 @@ class PromptCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
prompt_name: import_class(f"langchain.prompts.{prompt_name}")
|
prompt_name: import_class(f"langchain.prompts.{prompt_name}")
|
||||||
|
|
@ -34,7 +36,8 @@ 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_manager.settings.PROMPTS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain import retrievers
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_method, build_template_from_class
|
from langflow.utils.util import build_template_from_method, build_template_from_class
|
||||||
|
|
@ -48,10 +49,12 @@ class RetrieverCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.RETRIEVERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from langflow.cache.utils import memoize_dict
|
from langflow.services.cache.utils import memoize_dict
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
|
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
|
||||||
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
|
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
@ -30,10 +31,12 @@ class TextSplitterCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.TEXTSPLITTERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain.agents import agent_toolkits
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class, import_module
|
from langflow.interface.importing.utils import import_class, import_module
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
@ -29,13 +30,15 @@ class ToolkitCreator(LangChainTypeCreator):
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
toolkit_name: import_class(
|
toolkit_name: import_class(
|
||||||
f"langchain.agents.agent_toolkits.{toolkit_name}"
|
f"langchain.agents.agent_toolkits.{toolkit_name}"
|
||||||
)
|
)
|
||||||
# if toolkit_name is not lower case it is a class
|
# if toolkit_name is not lower case it is a class
|
||||||
for toolkit_name in agent_toolkits.__all__
|
for toolkit_name in agent_toolkits.__all__
|
||||||
if not toolkit_name.islower() and toolkit_name in settings.toolkits
|
if not toolkit_name.islower()
|
||||||
|
and toolkit_name in settings_manager.settings.TOOLKITS
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ from langflow.interface.tools.constants import (
|
||||||
OTHER_TOOLS,
|
OTHER_TOOLS,
|
||||||
)
|
)
|
||||||
from langflow.interface.tools.util import get_tool_params
|
from langflow.interface.tools.util import get_tool_params
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.template.base import Template
|
from langflow.template.template.base import Template
|
||||||
from langflow.utils import util
|
from langflow.utils import util
|
||||||
|
|
@ -66,6 +67,7 @@ class ToolCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
if self.tools_dict is None:
|
if self.tools_dict is None:
|
||||||
all_tools = {}
|
all_tools = {}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,10 @@ 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_manager.settings.TOOLS
|
||||||
|
or settings_manager.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] = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
import ast
|
||||||
|
import contextlib
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from langflow.api.utils import merge_nested_dicts_with_renaming
|
||||||
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
|
||||||
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||||
|
|
@ -28,7 +31,6 @@ from langflow.interface.retrievers.base import retriever_creator
|
||||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import get_base_classes
|
from langflow.utils.util import get_base_classes
|
||||||
from langflow.api.utils import merge_nested_dicts
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
@ -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)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -388,4 +423,4 @@ def build_langchain_custom_component_list_from_path(path: str):
|
||||||
valid_menu = build_valid_menu(valid_components)
|
valid_menu = build_valid_menu(valid_components)
|
||||||
invalid_menu = build_invalid_menu(invalid_components)
|
invalid_menu = build_invalid_menu(invalid_components)
|
||||||
|
|
||||||
return merge_nested_dicts(valid_menu, invalid_menu)
|
return merge_nested_dicts_with_renaming(valid_menu, invalid_menu)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from langchain import SQLDatabase, utilities
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
|
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
@ -26,6 +27,7 @@ class UtilityCreator(LangChainTypeCreator):
|
||||||
from the langchain.chains module and filtering them according to the settings.utilities list.
|
from the langchain.chains module and filtering them according to the settings.utilities list.
|
||||||
"""
|
"""
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
utility_name: import_class(f"langchain.utilities.{utility_name}")
|
utility_name: import_class(f"langchain.utilities.{utility_name}")
|
||||||
for utility_name in utilities.__all__
|
for utility_name in utilities.__all__
|
||||||
|
|
@ -35,7 +37,8 @@ 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_manager.settings.UTILITIES
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import yaml
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.chat.config import ChatConfig
|
from langflow.services.chat.config import ChatConfig
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
|
|
||||||
def load_file_into_dict(file_path: str) -> dict:
|
def load_file_into_dict(file_path: str) -> dict:
|
||||||
|
|
@ -63,24 +64,21 @@ def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||||
|
|
||||||
def setup_llm_caching():
|
def setup_llm_caching():
|
||||||
"""Setup LLM caching."""
|
"""Setup LLM caching."""
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
from langflow.settings import settings
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
set_langchain_cache(settings)
|
set_langchain_cache(settings_manager.settings)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning(f"Could not import {settings.cache}. ")
|
logger.warning(f"Could not import {settings_manager.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()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain import vectorstores
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
|
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_method
|
from langflow.utils.util import build_template_from_method
|
||||||
|
|
@ -43,10 +44,12 @@ class VectorstoreCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
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_manager.settings.VECTORSTORES
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@ from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
from langflow.routers import login, users, health
|
from langflow.routers import login, users, health
|
||||||
from langflow.database.base import create_db_and_tables
|
|
||||||
from langflow.interface.utils import setup_llm_caching
|
from langflow.interface.utils import setup_llm_caching
|
||||||
|
from langflow.services.database.utils import initialize_database
|
||||||
|
from langflow.services.manager import initialize_services
|
||||||
from langflow.utils.logger import configure
|
from langflow.utils.logger import configure
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
"""Create the FastAPI app and include the router."""
|
"""Create the FastAPI app and include the router."""
|
||||||
|
|
||||||
configure()
|
configure()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
@ -31,9 +34,11 @@ def create_app():
|
||||||
app.include_router(login.router)
|
app.include_router(login.router)
|
||||||
app.include_router(users.router)
|
app.include_router(users.router)
|
||||||
app.include_router(health.router)
|
app.include_router(health.router)
|
||||||
|
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
|
|
||||||
app.on_event("startup")(create_db_and_tables)
|
app.on_event("startup")(initialize_services)
|
||||||
|
app.on_event("startup")(initialize_database)
|
||||||
app.on_event("startup")(setup_llm_caching)
|
app.on_event("startup")(setup_llm_caching)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
@ -66,16 +71,20 @@ def get_static_files_dir():
|
||||||
return frontend_path / "frontend"
|
return frontend_path / "frontend"
|
||||||
|
|
||||||
|
|
||||||
def setup_app(static_files_dir: Optional[Path] = None) -> FastAPI:
|
def setup_app(
|
||||||
|
static_files_dir: Optional[Path] = None, backend_only: bool = False
|
||||||
|
) -> FastAPI:
|
||||||
"""Setup the FastAPI app."""
|
"""Setup the FastAPI app."""
|
||||||
# get the directory of the current file
|
# get the directory of the current file
|
||||||
if not static_files_dir:
|
if not static_files_dir:
|
||||||
static_files_dir = get_static_files_dir()
|
static_files_dir = get_static_files_dir()
|
||||||
|
|
||||||
if not static_files_dir or not static_files_dir.exists():
|
if not backend_only and (not static_files_dir or not static_files_dir.exists()):
|
||||||
raise RuntimeError(f"Static files directory {static_files_dir} does not exist.")
|
raise RuntimeError(
|
||||||
|
f"Static files directory {static_files_dir} does not exist.")
|
||||||
app = create_app()
|
app = create_app()
|
||||||
setup_static_files(app, static_files_dir)
|
if not backend_only and static_files_dir is not None:
|
||||||
|
setup_static_files(app, static_files_dir)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ from langflow.auth.auth import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
@ -18,7 +19,8 @@ router = APIRouter()
|
||||||
def create_user_token(user: str) -> dict:
|
def create_user_token(user: str) -> dict:
|
||||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
access_token = create_access_token(
|
access_token = create_access_token(
|
||||||
data={"sub": user.username}, expires_delta=access_token_expires # type: ignore
|
# type: ignore
|
||||||
|
data={"sub": user.username}, expires_delta=access_token_expires
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"access_token": access_token, "token_type": "bearer"}
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from sqlmodel import Session, select
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
from langflow.auth.auth import get_current_active_user
|
from langflow.auth.auth import get_current_active_user
|
||||||
from langflow.database.models.user import UserAddModel, UserListModel, User
|
from langflow.database.models.user import UserAddModel, UserListModel, User
|
||||||
|
|
||||||
|
|
|
||||||
4
src/backend/langflow/services/__init__.py
Normal file
4
src/backend/langflow/services/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .manager import service_manager
|
||||||
|
from .schema import ServiceType
|
||||||
|
|
||||||
|
__all__ = ["service_manager", "ServiceType"]
|
||||||
2
src/backend/langflow/services/base.py
Normal file
2
src/backend/langflow/services/base.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
class Service:
|
||||||
|
name: str
|
||||||
11
src/backend/langflow/services/cache/__init__.py
vendored
Normal file
11
src/backend/langflow/services/cache/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from . import factory, manager
|
||||||
|
from langflow.services.cache.manager import cache_manager
|
||||||
|
from langflow.services.cache.flow import InMemoryCache
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"cache_manager",
|
||||||
|
"factory",
|
||||||
|
"manager",
|
||||||
|
"InMemoryCache",
|
||||||
|
]
|
||||||
11
src/backend/langflow/services/cache/factory.py
vendored
Normal file
11
src/backend/langflow/services/cache/factory.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from langflow.services.cache.manager import CacheManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class CacheManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(CacheManager)
|
||||||
|
|
||||||
|
def create(self, settings_service):
|
||||||
|
# Here you would have logic to create and configure a CacheManager
|
||||||
|
return CacheManager()
|
||||||
|
|
@ -2,7 +2,7 @@ import threading
|
||||||
import time
|
import time
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from langflow.cache.base import BaseCache
|
from langflow.services.cache.base import BaseCache
|
||||||
|
|
||||||
|
|
||||||
class InMemoryCache(BaseCache):
|
class InMemoryCache(BaseCache):
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Any, Awaitable, Callable, List, Optional
|
from typing import Any, Awaitable, Callable, List, Optional
|
||||||
|
from langflow.services.base import Service
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
@ -49,9 +50,11 @@ class AsyncSubject:
|
||||||
await observer()
|
await observer()
|
||||||
|
|
||||||
|
|
||||||
class CacheManager(Subject):
|
class CacheManager(Subject, Service):
|
||||||
"""Manages cache for different clients and notifies observers on changes."""
|
"""Manages cache for different clients and notifies observers on changes."""
|
||||||
|
|
||||||
|
name = "cache_manager"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
11
src/backend/langflow/services/chat/factory.py
Normal file
11
src/backend/langflow/services/chat/factory.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from langflow.services.chat.manager import ChatManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class ChatManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(ChatManager)
|
||||||
|
|
||||||
|
def create(self, settings_service):
|
||||||
|
# Here you would have logic to create and configure a ChatManager
|
||||||
|
return ChatManager()
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from fastapi import WebSocket, status
|
from fastapi import WebSocket, status
|
||||||
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
|
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
|
||||||
from langflow.cache import cache_manager
|
from langflow.services.base import Service
|
||||||
from langflow.cache.manager import Subject
|
from langflow.services import service_manager
|
||||||
from langflow.chat.utils import process_graph
|
from langflow.services.cache.manager import Subject
|
||||||
|
from langflow.services.chat.utils import process_graph
|
||||||
from langflow.interface.utils import pil_to_base64
|
from langflow.interface.utils import pil_to_base64
|
||||||
|
from langflow.services.schema import ServiceType
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,7 +14,7 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from langflow.cache.flow import InMemoryCache
|
from langflow.services.cache.flow import InMemoryCache
|
||||||
|
|
||||||
|
|
||||||
class ChatHistory(Subject):
|
class ChatHistory(Subject):
|
||||||
|
|
@ -42,11 +44,13 @@ class ChatHistory(Subject):
|
||||||
self.history[client_id] = []
|
self.history[client_id] = []
|
||||||
|
|
||||||
|
|
||||||
class ChatManager:
|
class ChatManager(Service):
|
||||||
|
name = "chat_manager"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.active_connections: Dict[str, WebSocket] = {}
|
self.active_connections: Dict[str, WebSocket] = {}
|
||||||
self.chat_history = ChatHistory()
|
self.chat_history = ChatHistory()
|
||||||
self.cache_manager = cache_manager
|
self.cache_manager = service_manager.get(ServiceType.CACHE_MANAGER)
|
||||||
self.cache_manager.attach(self.update)
|
self.cache_manager.attach(self.update)
|
||||||
self.in_memory_cache = InMemoryCache()
|
self.in_memory_cache = InMemoryCache()
|
||||||
|
|
||||||
|
|
@ -117,7 +121,7 @@ class ChatManager:
|
||||||
self, client_id: str, payload: Dict, langchain_object: Any
|
self, client_id: str, payload: Dict, langchain_object: Any
|
||||||
):
|
):
|
||||||
# Process the graph data and chat message
|
# Process the graph data and chat message
|
||||||
chat_inputs = payload.pop("inputs", "")
|
chat_inputs = payload.pop("inputs", {})
|
||||||
chat_inputs = ChatMessage(message=chat_inputs)
|
chat_inputs = ChatMessage(message=chat_inputs)
|
||||||
self.chat_history.add_message(client_id, chat_inputs)
|
self.chat_history.add_message(client_id, chat_inputs)
|
||||||
|
|
||||||
|
|
@ -21,9 +21,9 @@ async def process_graph(
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
try:
|
try:
|
||||||
if not chat_inputs.message:
|
if chat_inputs.message is None:
|
||||||
logger.debug("No message provided")
|
logger.debug("No message provided")
|
||||||
raise ValueError("No message provided")
|
chat_inputs.message = {}
|
||||||
|
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, intermediate_steps = await get_result_and_steps(
|
result, intermediate_steps = await get_result_and_steps(
|
||||||
17
src/backend/langflow/services/database/factory.py
Normal file
17
src/backend/langflow/services/database/factory.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from langflow.services.database.manager import DatabaseManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.settings.manager import SettingsManager
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(DatabaseManager)
|
||||||
|
|
||||||
|
def create(self, settings_service: "SettingsManager"):
|
||||||
|
# Here you would have logic to create and configure a DatabaseManager
|
||||||
|
if not settings_service.settings.DATABASE_URL:
|
||||||
|
raise ValueError("No database URL provided")
|
||||||
|
return DatabaseManager(settings_service.settings.DATABASE_URL)
|
||||||
67
src/backend/langflow/services/database/manager.py
Normal file
67
src/backend/langflow/services/database/manager.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from langflow.services.base import Service
|
||||||
|
from sqlmodel import SQLModel, Session, create_engine
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from alembic.config import Config
|
||||||
|
from alembic import command
|
||||||
|
from langflow.services.database import models # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager(Service):
|
||||||
|
name = "database_manager"
|
||||||
|
|
||||||
|
def __init__(self, database_url: str):
|
||||||
|
self.database_url = database_url
|
||||||
|
# This file is in langflow.services.database.manager.py
|
||||||
|
# the ini is in langflow
|
||||||
|
langflow_dir = Path(__file__).parent.parent.parent
|
||||||
|
self.script_location = langflow_dir / "alembic"
|
||||||
|
self.alembic_cfg_path = langflow_dir / "alembic.ini"
|
||||||
|
self.engine = create_engine(database_url)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._session = Session(self.engine)
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
if exc_type is not None: # If an exception has been raised
|
||||||
|
logger.error(
|
||||||
|
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
|
||||||
|
)
|
||||||
|
self._session.rollback()
|
||||||
|
else:
|
||||||
|
self._session.commit()
|
||||||
|
self._session.close()
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
yield session
|
||||||
|
|
||||||
|
def run_migrations(self):
|
||||||
|
logger.info(
|
||||||
|
f"Running DB migrations in {self.script_location} on {self.database_url}"
|
||||||
|
)
|
||||||
|
alembic_cfg = Config()
|
||||||
|
alembic_cfg.set_main_option("script_location", str(self.script_location))
|
||||||
|
alembic_cfg.set_main_option("sqlalchemy.url", self.database_url)
|
||||||
|
command.upgrade(alembic_cfg, "head")
|
||||||
|
|
||||||
|
def create_db_and_tables(self):
|
||||||
|
logger.debug("Creating database and tables")
|
||||||
|
try:
|
||||||
|
SQLModel.metadata.create_all(self.engine)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error creating database and tables: {exc}")
|
||||||
|
raise RuntimeError("Error creating database and tables") from exc
|
||||||
|
|
||||||
|
# Now check if the table "flow" exists, if not, something went wrong
|
||||||
|
# and we need to create the tables again.
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
inspector = inspect(self.engine)
|
||||||
|
if "flow" not in inspector.get_table_names():
|
||||||
|
logger.error("Something went wrong creating the database and tables.")
|
||||||
|
logger.error("Please check your database settings.")
|
||||||
|
raise RuntimeError("Something went wrong creating the database and tables.")
|
||||||
|
else:
|
||||||
|
logger.debug("Database and tables created successfully")
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .flow import Flow
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Flow"]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from langflow.database.models.base import SQLModelSerializable, SQLModel
|
from langflow.services.database.models.base import SQLModelSerializable, SQLModel
|
||||||
from sqlmodel import Field
|
from sqlmodel import Field
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
# Path: src/backend/langflow/database/models/flow.py
|
# Path: src/backend/langflow/database/models/flow.py
|
||||||
|
|
||||||
from langflow.database.models.base import SQLModelSerializable
|
from langflow.services.database.models.base import SQLModelSerializable
|
||||||
from pydantic import validator
|
from pydantic import validator
|
||||||
from sqlmodel import Field, Relationship, JSON, Column
|
from sqlmodel import Field, JSON, Column
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
# if TYPE_CHECKING:
|
# if TYPE_CHECKING:
|
||||||
from langflow.database.models.flow_style import FlowStyle, FlowStyleRead
|
|
||||||
|
|
||||||
|
|
||||||
class FlowBase(SQLModelSerializable):
|
class FlowBase(SQLModelSerializable):
|
||||||
|
|
@ -35,11 +34,6 @@ class FlowBase(SQLModelSerializable):
|
||||||
class Flow(FlowBase, table=True):
|
class Flow(FlowBase, table=True):
|
||||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
||||||
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
|
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
|
||||||
style: Optional["FlowStyle"] = Relationship(
|
|
||||||
back_populates="flow",
|
|
||||||
# use "uselist=False" to make it a one-to-one relationship
|
|
||||||
sa_relationship_kwargs={"uselist": False},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FlowCreate(FlowBase):
|
class FlowCreate(FlowBase):
|
||||||
|
|
@ -50,10 +44,6 @@ class FlowRead(FlowBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
|
|
||||||
|
|
||||||
class FlowReadWithStyle(FlowRead):
|
|
||||||
style: Optional["FlowStyleRead"] = None
|
|
||||||
|
|
||||||
|
|
||||||
class FlowUpdate(SQLModelSerializable):
|
class FlowUpdate(SQLModelSerializable):
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
31
src/backend/langflow/services/database/utils.py
Normal file
31
src/backend/langflow/services/database/utils.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.database.manager import DatabaseManager
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_database():
|
||||||
|
logger.debug("Initializing database")
|
||||||
|
from langflow.services import service_manager, ServiceType
|
||||||
|
|
||||||
|
database_manager = service_manager.get(ServiceType.DATABASE_MANAGER)
|
||||||
|
database_manager.run_migrations()
|
||||||
|
database_manager.create_db_and_tables()
|
||||||
|
logger.debug("Database initialized")
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def session_getter(db_manager: "DatabaseManager"):
|
||||||
|
try:
|
||||||
|
session = Session(db_manager.engine)
|
||||||
|
yield session
|
||||||
|
except Exception as e:
|
||||||
|
print("Session rollback because of exception:", e)
|
||||||
|
session.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
6
src/backend/langflow/services/factory.py
Normal file
6
src/backend/langflow/services/factory.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
class ServiceFactory:
|
||||||
|
def __init__(self, service_class):
|
||||||
|
self.service_class = service_class
|
||||||
|
|
||||||
|
def create(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError
|
||||||
87
src/backend/langflow/services/manager.py
Normal file
87
src/backend/langflow/services/manager.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
from langflow.services.schema import ServiceType
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceManager:
|
||||||
|
"""
|
||||||
|
Manages the creation of different services.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.services = {}
|
||||||
|
self.factories = {}
|
||||||
|
|
||||||
|
def register_factory(self, service_factory: "ServiceFactory"):
|
||||||
|
"""
|
||||||
|
Registers a new factory.
|
||||||
|
"""
|
||||||
|
self.factories[service_factory.service_class.name] = service_factory
|
||||||
|
|
||||||
|
def get(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Get (or create) a service by its name.
|
||||||
|
"""
|
||||||
|
if service_name not in self.services:
|
||||||
|
self._create_service(service_name)
|
||||||
|
|
||||||
|
return self.services[service_name]
|
||||||
|
|
||||||
|
def _create_service(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Create a new service given its name.
|
||||||
|
"""
|
||||||
|
self._validate_service_creation(service_name)
|
||||||
|
|
||||||
|
if service_name == ServiceType.SETTINGS_MANAGER:
|
||||||
|
self.services[service_name] = self.factories[service_name].create()
|
||||||
|
else:
|
||||||
|
settings_service = self.get(ServiceType.SETTINGS_MANAGER)
|
||||||
|
self.services[service_name] = self.factories[service_name].create(
|
||||||
|
settings_service
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_service_creation(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Validate whether the service can be created.
|
||||||
|
"""
|
||||||
|
if service_name not in self.factories:
|
||||||
|
raise ValueError(
|
||||||
|
f"No factory registered for the service class '{service_name.name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ServiceType.SETTINGS_MANAGER not in self.factories
|
||||||
|
and service_name != ServiceType.SETTINGS_MANAGER
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"Cannot create service '{service_name.name}' before the settings service"
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Update a service by its name.
|
||||||
|
"""
|
||||||
|
if service_name in self.services:
|
||||||
|
self.services.pop(service_name, None)
|
||||||
|
self.get(service_name)
|
||||||
|
|
||||||
|
|
||||||
|
service_manager = ServiceManager()
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_services():
|
||||||
|
"""
|
||||||
|
Initialize all the services needed.
|
||||||
|
"""
|
||||||
|
from langflow.services.database import factory as database_factory
|
||||||
|
from langflow.services.cache import factory as cache_factory
|
||||||
|
from langflow.services.chat import factory as chat_factory
|
||||||
|
from langflow.services.settings import factory as settings_factory
|
||||||
|
|
||||||
|
service_manager.register_factory(settings_factory.SettingsManagerFactory())
|
||||||
|
service_manager.register_factory(database_factory.DatabaseManagerFactory())
|
||||||
|
service_manager.register_factory(cache_factory.CacheManagerFactory())
|
||||||
|
service_manager.register_factory(chat_factory.ChatManagerFactory())
|
||||||
13
src/backend/langflow/services/schema.py
Normal file
13
src/backend/langflow/services/schema.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceType(str, Enum):
|
||||||
|
"""
|
||||||
|
Enum for the different types of services that can be
|
||||||
|
registered with the service manager.
|
||||||
|
"""
|
||||||
|
|
||||||
|
CACHE_MANAGER = "cache_manager"
|
||||||
|
SETTINGS_MANAGER = "settings_manager"
|
||||||
|
DATABASE_MANAGER = "database_manager"
|
||||||
|
CHAT_MANAGER = "chat_manager"
|
||||||
3
src/backend/langflow/services/settings/__init__.py
Normal file
3
src/backend/langflow/services/settings/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from . import factory, manager
|
||||||
|
|
||||||
|
__all__ = ["factory", "manager"]
|
||||||
168
src/backend/langflow/services/settings/base.py
Normal file
168
src/backend/langflow/services/settings/base.py
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
import contextlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Optional, List
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from pydantic import BaseSettings, root_validator, validator
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
CHAINS: dict = {}
|
||||||
|
AGENTS: dict = {}
|
||||||
|
PROMPTS: dict = {}
|
||||||
|
LLMS: dict = {}
|
||||||
|
TOOLS: dict = {}
|
||||||
|
MEMORIES: dict = {}
|
||||||
|
EMBEDDINGS: dict = {}
|
||||||
|
VECTORSTORES: dict = {}
|
||||||
|
DOCUMENTLOADERS: dict = {}
|
||||||
|
WRAPPERS: dict = {}
|
||||||
|
RETRIEVERS: dict = {}
|
||||||
|
TOOLKITS: dict = {}
|
||||||
|
TEXTSPLITTERS: dict = {}
|
||||||
|
UTILITIES: dict = {}
|
||||||
|
OUTPUT_PARSERS: dict = {}
|
||||||
|
CUSTOM_COMPONENTS: dict = {}
|
||||||
|
|
||||||
|
DEV: bool = False
|
||||||
|
DATABASE_URL: Optional[str] = None
|
||||||
|
CACHE: str = "InMemoryCache"
|
||||||
|
REMOVE_API_KEYS: bool = False
|
||||||
|
COMPONENTS_PATH: List[str] = []
|
||||||
|
|
||||||
|
@validator("DATABASE_URL", pre=True)
|
||||||
|
def set_database_url(cls, value):
|
||||||
|
if not value:
|
||||||
|
logger.debug(
|
||||||
|
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
|
||||||
|
)
|
||||||
|
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
||||||
|
value = langflow_database_url
|
||||||
|
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
||||||
|
else:
|
||||||
|
logger.debug("No DATABASE_URL env variable, using sqlite database")
|
||||||
|
value = "sqlite:///./langflow.db"
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
@validator("COMPONENTS_PATH", pre=True)
|
||||||
|
def set_components_path(cls, value):
|
||||||
|
if os.getenv("LANGFLOW_COMPONENTS_PATH"):
|
||||||
|
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
|
||||||
|
langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
|
||||||
|
if (
|
||||||
|
Path(langflow_component_path).exists()
|
||||||
|
and langflow_component_path not in value
|
||||||
|
):
|
||||||
|
if isinstance(langflow_component_path, list):
|
||||||
|
for path in langflow_component_path:
|
||||||
|
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:
|
||||||
|
validate_assignment = True
|
||||||
|
extra = "ignore"
|
||||||
|
env_prefix = "LANGFLOW_"
|
||||||
|
|
||||||
|
@root_validator(allow_reuse=True)
|
||||||
|
def validate_lists(cls, values):
|
||||||
|
for key, value in values.items():
|
||||||
|
if key != "dev" and not value:
|
||||||
|
values[key] = []
|
||||||
|
return values
|
||||||
|
|
||||||
|
def update_from_yaml(self, file_path: str, dev: bool = False):
|
||||||
|
new_settings = load_settings_from_yaml(file_path)
|
||||||
|
self.CHAINS = new_settings.CHAINS or {}
|
||||||
|
self.AGENTS = new_settings.AGENTS or {}
|
||||||
|
self.PROMPTS = new_settings.PROMPTS or {}
|
||||||
|
self.LLMS = new_settings.LLMS or {}
|
||||||
|
self.TOOLS = new_settings.TOOLS or {}
|
||||||
|
self.MEMORIES = new_settings.MEMORIES or {}
|
||||||
|
self.WRAPPERS = new_settings.WRAPPERS or {}
|
||||||
|
self.TOOLKITS = new_settings.TOOLKITS or {}
|
||||||
|
self.TEXTSPLITTERS = new_settings.TEXTSPLITTERS or {}
|
||||||
|
self.UTILITIES = new_settings.UTILITIES or {}
|
||||||
|
self.EMBEDDINGS = new_settings.EMBEDDINGS or {}
|
||||||
|
self.VECTORSTORES = new_settings.VECTORSTORES or {}
|
||||||
|
self.DOCUMENTLOADERS = new_settings.DOCUMENTLOADERS or {}
|
||||||
|
self.RETRIEVERS = new_settings.RETRIEVERS or {}
|
||||||
|
self.OUTPUT_PARSERS = new_settings.OUTPUT_PARSERS or {}
|
||||||
|
self.CUSTOM_COMPONENTS = new_settings.CUSTOM_COMPONENTS or {}
|
||||||
|
self.COMPONENTS_PATH = new_settings.COMPONENTS_PATH or []
|
||||||
|
self.DEV = dev
|
||||||
|
|
||||||
|
def update_settings(self, **kwargs):
|
||||||
|
logger.debug("Updating settings")
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
# value may contain sensitive information, so we don't want to log it
|
||||||
|
if not hasattr(self, key):
|
||||||
|
logger.debug(f"Key {key} not found in settings")
|
||||||
|
continue
|
||||||
|
logger.debug(f"Updating {key}")
|
||||||
|
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:
|
||||||
|
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):
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
settings_dict = settings.dict()
|
||||||
|
yaml.dump(settings_dict, f)
|
||||||
|
|
||||||
|
|
||||||
|
def load_settings_from_yaml(file_path: str) -> Settings:
|
||||||
|
# Check if a string is a valid path or a file name
|
||||||
|
if "/" not in file_path:
|
||||||
|
# Get current path
|
||||||
|
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
file_path = os.path.join(current_path, file_path)
|
||||||
|
|
||||||
|
with open(file_path, "r") as 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)
|
||||||
15
src/backend/langflow/services/settings/factory.py
Normal file
15
src/backend/langflow/services/settings/factory.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from langflow.services.settings.manager import SettingsManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(SettingsManager)
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
# Here you would have logic to create and configure a SettingsManager
|
||||||
|
langflow_dir = Path(__file__).parent.parent.parent
|
||||||
|
return SettingsManager.load_settings_from_yaml(
|
||||||
|
str(langflow_dir / "config.yaml")
|
||||||
|
)
|
||||||
36
src/backend/langflow/services/settings/manager.py
Normal file
36
src/backend/langflow/services/settings/manager.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
from langflow.services.base import Service
|
||||||
|
from langflow.services.settings.base import Settings
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsManager(Service):
|
||||||
|
name = "settings_manager"
|
||||||
|
|
||||||
|
def __init__(self, settings: Settings):
|
||||||
|
super().__init__()
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_settings_from_yaml(cls, file_path: str) -> "SettingsManager":
|
||||||
|
# Check if a string is a valid path or a file name
|
||||||
|
if "/" not in file_path:
|
||||||
|
# Get current path
|
||||||
|
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
file_path = os.path.join(current_path, file_path)
|
||||||
|
|
||||||
|
with open(file_path, "r") as 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}"
|
||||||
|
)
|
||||||
|
|
||||||
|
settings = Settings(**settings_dict)
|
||||||
|
return cls(settings)
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue