Merge branch 'dev' into lf-docs-fix
This commit is contained in:
commit
032d0eb65a
219 changed files with 15097 additions and 7379 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# LangFlow Demo Codespace Readme
|
# Langflow Demo Codespace Readme
|
||||||
|
|
||||||
These instructions will walk you through the process of running a LangFlow demo via GitHub Codespaces.
|
These instructions will walk you through the process of running a Langflow demo via GitHub Codespaces.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|
|
||||||
47
.env.example
Normal file
47
.env.example
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Description: Example of .env file
|
||||||
|
# Usage: Copy this file to .env and change the values
|
||||||
|
# according to your needs
|
||||||
|
# Do not commit .env file to git
|
||||||
|
# Do not change .env.example file
|
||||||
|
|
||||||
|
# Database URL
|
||||||
|
# Postgres example: LANGFLOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/langflow
|
||||||
|
# SQLite example:
|
||||||
|
LANGFLOW_DATABASE_URL=sqlite:///./langflow.db
|
||||||
|
|
||||||
|
# Cache type
|
||||||
|
LANGFLOW_LANGCHAIN_CACHE=SQLiteCache
|
||||||
|
|
||||||
|
# Server host
|
||||||
|
# Example: LANGFLOW_HOST=127.0.0.1
|
||||||
|
LANGFLOW_HOST=
|
||||||
|
|
||||||
|
# Worker processes
|
||||||
|
# Example: LANGFLOW_WORKERS=1
|
||||||
|
LANGFLOW_WORKERS=
|
||||||
|
|
||||||
|
# Server port
|
||||||
|
# Example: LANGFLOW_PORT=7860
|
||||||
|
LANGFLOW_PORT=
|
||||||
|
|
||||||
|
# Logging level
|
||||||
|
# Example: LANGFLOW_LOG_LEVEL=critical
|
||||||
|
LANGFLOW_LOG_LEVEL=
|
||||||
|
|
||||||
|
# Path to the log file
|
||||||
|
# Example: LANGFLOW_LOG_FILE=logs/langflow.log
|
||||||
|
LANGFLOW_LOG_FILE=
|
||||||
|
|
||||||
|
# Path to the frontend directory containing build files
|
||||||
|
# Example: LANGFLOW_FRONTEND_PATH=/path/to/frontend/build/files
|
||||||
|
LANGFLOW_FRONTEND_PATH=
|
||||||
|
|
||||||
|
# Whether to open the browser after starting the server
|
||||||
|
# Values: true, false
|
||||||
|
# Example: LANGFLOW_OPEN_BROWSER=true
|
||||||
|
LANGFLOW_OPEN_BROWSER=
|
||||||
|
|
||||||
|
# Whether to remove API keys from the projects saved in the database
|
||||||
|
# Values: true, false
|
||||||
|
# Example: LANGFLOW_REMOVE_API_KEYS=false
|
||||||
|
LANGFLOW_REMOVE_API_KEYS=
|
||||||
0
.githooks/pre-commit
Normal file → Executable file
0
.githooks/pre-commit
Normal file → Executable file
66
.github/workflows/codeql.yml
vendored
Normal file
66
.github/workflows/codeql.yml
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ 'dev', 'main' ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ 'dev' ]
|
||||||
|
schedule:
|
||||||
|
- cron: '17 2 * * 1'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'python', 'javascript' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||||
|
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||||
|
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
|
# This is to avoid Opencommit hook from getting pushed
|
||||||
|
prepare-commit-msg
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
@ -243,5 +245,9 @@ dmypy.json
|
||||||
.testenv/*
|
.testenv/*
|
||||||
langflow.db
|
langflow.db
|
||||||
|
|
||||||
|
|
||||||
|
.githooks/prepare-commit-msg
|
||||||
|
.langchain.db
|
||||||
|
|
||||||
# docusaurus
|
# docusaurus
|
||||||
.docusaurus/
|
.docusaurus/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Contributing to LangFlow
|
# Contributing to Langflow
|
||||||
|
|
||||||
Hello there! We appreciate your interest in contributing to LangFlow.
|
Hello there! We appreciate your interest in contributing to Langflow.
|
||||||
As an open-source project in a rapidly developing field, we are extremely open
|
As an open-source project in a rapidly developing field, we are extremely open
|
||||||
to contributions, whether it be in the form of a new feature, improved infra, or better documentation.
|
to contributions, whether it be in the form of a new feature, improved infra, or better documentation.
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ the system we use to tag our issues and pull requests.
|
||||||
|
|
||||||
|
|
||||||
### Local development
|
### Local development
|
||||||
You can develop LangFlow using docker compose, or locally.
|
You can develop Langflow using docker compose, or locally.
|
||||||
|
|
||||||
We provide a .vscode/launch.json file for debugging the backend in VSCode, which is a lot faster than using docker compose.
|
We provide a .vscode/launch.json file for debugging the backend in VSCode, which is a lot faster than using docker compose.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ This script sets up a Debian-based VM with the Langflow package, Nginx, and the
|
||||||
|
|
||||||
## Spot/Preemptible Instance
|
## Spot/Preemptible Instance
|
||||||
|
|
||||||
[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/genome21/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
|
[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
|
||||||
|
|
||||||
When running as a [spot (preemptible) instance](https://cloud.google.com/compute/docs/instances/preemptible), the code and VM will behave the same way as in a regular instance, executing the startup script to configure the environment, install necessary dependencies, and run the Langflow application. However, **due to the nature of spot instances, the VM may be terminated at any time if Google Cloud needs to reclaim the resources**. This makes spot instances suitable for fault-tolerant, stateless, or interruptible workloads that can handle unexpected terminations and restarts.
|
When running as a [spot (preemptible) instance](https://cloud.google.com/compute/docs/instances/preemptible), the code and VM will behave the same way as in a regular instance, executing the startup script to configure the environment, install necessary dependencies, and run the Langflow application. However, **due to the nature of spot instances, the VM may be terminated at any time if Google Cloud needs to reclaim the resources**. This makes spot instances suitable for fault-tolerant, stateless, or interruptible workloads that can handle unexpected terminations and restarts.
|
||||||
|
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -5,6 +5,8 @@ all: help
|
||||||
init:
|
init:
|
||||||
@echo 'Installing pre-commit hooks'
|
@echo 'Installing pre-commit hooks'
|
||||||
git config core.hooksPath .githooks
|
git config core.hooksPath .githooks
|
||||||
|
@echo 'Making pre-commit hook executable'
|
||||||
|
chmod +x .githooks/pre-commit
|
||||||
@echo 'Installing backend dependencies'
|
@echo 'Installing backend dependencies'
|
||||||
make install_backend
|
make install_backend
|
||||||
@echo 'Installing frontend dependencies'
|
@echo 'Installing frontend dependencies'
|
||||||
|
|
|
||||||
100
README.md
100
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
|
|
||||||
# ⛓️ LangFlow
|
# ⛓️ Langflow
|
||||||
|
|
||||||
~ An effortless way to experiment and prototype [LangChain](https://github.com/hwchase17/langchain) pipelines ~
|
~ An effortless way to experiment and prototype [LangChain](https://github.com/hwchase17/langchain) pipelines ~
|
||||||
|
|
||||||
|
|
@ -13,9 +13,10 @@
|
||||||
<img alt="Github License" src="https://img.shields.io/github/license/logspace-ai/langflow" />
|
<img alt="Github License" src="https://img.shields.io/github/license/logspace-ai/langflow" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://discord.gg/FUhJnnJ9"><img alt="Discord Server" src="https://dcbadge.vercel.app/api/server/FUhJnnJ9?compact=true&style=flat"/></a>
|
<a href="https://discord.gg/EqksyE2EX9"><img alt="Discord Server" src="https://dcbadge.vercel.app/api/server/EqksyE2EX9?compact=true&style=flat"/></a>
|
||||||
<a href="https://huggingface.co/spaces/Logspace/LangFlow"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg" alt="HuggingFace Spaces"></a>
|
<a href="https://huggingface.co/spaces/Logspace/Langflow"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg" alt="HuggingFace Spaces"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="https://github.com/logspace-ai/langflow">
|
<a href="https://github.com/logspace-ai/langflow">
|
||||||
|
|
@ -25,9 +26,27 @@
|
||||||
<p>
|
<p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 📦 Installation
|
# Table of Contents
|
||||||
|
- [⛓️ Langflow](#️-langflow)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [📦 Installation](#-installation)
|
||||||
|
- [Locally](#locally)
|
||||||
|
- [HuggingFace Spaces](#huggingface-spaces)
|
||||||
|
- [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Environment Variables](#environment-variables)
|
||||||
|
- [Deployment](#deployment)
|
||||||
|
- [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform)
|
||||||
|
- [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud)
|
||||||
|
- [API Usage](#api-usage)
|
||||||
|
- [🎨 Creating Flows](#-creating-flows)
|
||||||
|
- [👋 Contributing](#-contributing)
|
||||||
|
- [📄 License](#-license)
|
||||||
|
|
||||||
|
|
||||||
|
# 📦 Installation
|
||||||
### <b>Locally</b>
|
### <b>Locally</b>
|
||||||
You can install LangFlow from pip:
|
You can install Langflow from pip:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pip install langflow
|
pip install langflow
|
||||||
|
|
@ -40,10 +59,54 @@ python -m langflow
|
||||||
```
|
```
|
||||||
or
|
or
|
||||||
```shell
|
```shell
|
||||||
langflow
|
langflow # or langflow --help
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy Langflow on Google Cloud Platform
|
### HuggingFace Spaces
|
||||||
|
You can also check it out on [HuggingFace Spaces](https://huggingface.co/spaces/Logspace/Langflow) and run it in your browser! You can even clone it and have your own copy of Langflow to play with.
|
||||||
|
|
||||||
|
# 🖥️ Command Line Interface (CLI)
|
||||||
|
|
||||||
|
Langflow provides a command-line interface (CLI) for easy management and configuration.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
You can run the Langflow using the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
langflow [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
Each option is detailed below:
|
||||||
|
|
||||||
|
- `--help`: Displays all available options.
|
||||||
|
- `--host`: Defines the host to bind the server to. Can be set using the `LANGFLOW_HOST` environment variable. The default is `127.0.0.1`.
|
||||||
|
- `--workers`: Sets the number of worker processes. Can be set using the `LANGFLOW_WORKERS` environment variable. The default is `1`.
|
||||||
|
- `--timeout`: Sets the worker timeout in seconds. The default is `60`.
|
||||||
|
- `--port`: Sets the port to listen on. Can be set using the `LANGFLOW_PORT` environment variable. The default is `7860`.
|
||||||
|
- `--config`: Defines the path to the configuration file. The default is `config.yaml`.
|
||||||
|
- `--env-file`: Specifies the path to the .env file containing environment variables. The default is `.env`.
|
||||||
|
- `--log-level`: Defines the logging level. Can be set using the `LANGFLOW_LOG_LEVEL` environment variable. The default is `critical`.
|
||||||
|
- `--log-file`: Specifies the path to the log file. Can be set using the `LANGFLOW_LOG_FILE` environment variable. The default is `logs/langflow.log`.
|
||||||
|
- `--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`.
|
||||||
|
- `--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.
|
||||||
|
- `--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`.
|
||||||
|
- `--install-completion [bash|zsh|fish|powershell|pwsh]`: Installs completion for the specified shell.
|
||||||
|
- `--show-completion [bash|zsh|fish|powershell|pwsh]`: Shows completion for the specified shell, allowing you to copy it or customize the installation.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
You can configure many of the CLI options using environment variables. These can be exported in your operating system or added to a `.env` file and loaded using the `--env-file` option.
|
||||||
|
|
||||||
|
A sample `.env` file named `.env.example` is included with the project. Copy this file to a new file named `.env` and replace the example values with your actual settings. If you're setting values in both your OS and the `.env` file, the `.env` settings will take precedence.
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
|
||||||
|
## Deploy Langflow on Google Cloud Platform
|
||||||
|
|
||||||
Follow our step-by-step guide to deploy Langflow on Google Cloud Platform (GCP) using Google Cloud Shell. The guide is available in the [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) document.
|
Follow our step-by-step guide to deploy Langflow on Google Cloud Platform (GCP) using Google Cloud Shell. The guide is available in the [**Langflow in Google Cloud Platform**](GCP_DEPLOYMENT.md) document.
|
||||||
|
|
||||||
|
|
@ -52,7 +115,7 @@ Alternatively, click the **"Open in Cloud Shell"** button below to launch Google
|
||||||
[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
|
[](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/logspace-ai/langflow&working_dir=scripts&shellonly=true&tutorial=walkthroughtutorial_spot.md)
|
||||||
|
|
||||||
|
|
||||||
### Deploy Langflow on [Jina AI Cloud](https://github.com/jina-ai/langchain-serve)
|
## Deploy Langflow on [Jina AI Cloud](https://github.com/jina-ai/langchain-serve)
|
||||||
|
|
||||||
Langflow integrates with langchain-serve to provide a one-command deployment to Jina AI Cloud.
|
Langflow integrates with langchain-serve to provide a one-command deployment to Jina AI Cloud.
|
||||||
|
|
||||||
|
|
@ -159,10 +222,17 @@ print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))
|
||||||
|
|
||||||
> Read more about resource customization, cost, and management of Langflow apps on Jina AI Cloud in the **[langchain-serve](https://github.com/jina-ai/langchain-serve)** repository.
|
> Read more about resource customization, cost, and management of Langflow apps on Jina AI Cloud in the **[langchain-serve](https://github.com/jina-ai/langchain-serve)** repository.
|
||||||
|
|
||||||
|
## Deploy on Railway
|
||||||
|
[](https://railway.app/template/Emy2sU?referralCode=MnPSdg)
|
||||||
|
|
||||||
## 🎨 Creating Flows
|
## Deploy on Render
|
||||||
|
<a href="https://render.com/deploy?repo=https://github.com/logspace-ai/langflow/tree/main">
|
||||||
|
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" />
|
||||||
|
</a>
|
||||||
|
|
||||||
Creating flows with LangFlow is easy. Simply drag sidebar components onto the canvas and connect them together to create your pipeline. LangFlow provides a range of [LangChain components](https://langchain.readthedocs.io/en/latest/reference.html) to choose from, including LLMs, prompt serializers, agents, and chains.
|
# 🎨 Creating Flows
|
||||||
|
|
||||||
|
Creating flows with Langflow is easy. Simply drag sidebar components onto the canvas and connect them together to create your pipeline. Langflow provides a range of [LangChain components](https://langchain.readthedocs.io/en/latest/reference.html) to choose from, including LLMs, prompt serializers, agents, and chains.
|
||||||
|
|
||||||
Explore by editing prompt parameters, link chains and agents, track an agent's thought process, and export your flow.
|
Explore by editing prompt parameters, link chains and agents, track an agent's thought process, and export your flow.
|
||||||
|
|
||||||
|
|
@ -175,13 +245,13 @@ from langflow import load_flow_from_json
|
||||||
|
|
||||||
flow = load_flow_from_json("path/to/flow.json")
|
flow = load_flow_from_json("path/to/flow.json")
|
||||||
# Now you can use it like any chain
|
# Now you can use it like any chain
|
||||||
flow("Hey, have you heard of LangFlow?")
|
flow("Hey, have you heard of Langflow?")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 👋 Contributing
|
# 👋 Contributing
|
||||||
|
|
||||||
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! 🦾
|
||||||
|
|
@ -192,6 +262,6 @@ Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask question
|
||||||
[](https://star-history.com/#logspace-ai/langflow&Date)
|
[](https://star-history.com/#logspace-ai/langflow&Date)
|
||||||
|
|
||||||
|
|
||||||
## 📄 License
|
# 📄 License
|
||||||
|
|
||||||
LangFlow is released under the MIT License. See the LICENSE file for details.
|
Langflow is released under the MIT License. See the LICENSE file for details.
|
||||||
|
|
|
||||||
3323
poetry.lock
generated
3323
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.1.6"
|
version = "0.2.13"
|
||||||
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 = [
|
||||||
|
|
@ -22,25 +22,24 @@ include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
|
||||||
langflow = "langflow.__main__:main"
|
langflow = "langflow.__main__:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<3.12"
|
python = ">=3.9,<3.11"
|
||||||
fastapi = "^0.97.0"
|
fastapi = "^0.100.0"
|
||||||
uvicorn = "^0.22.0"
|
uvicorn = "^0.22.0"
|
||||||
beautifulsoup4 = "^4.11.2"
|
beautifulsoup4 = "^4.12.2"
|
||||||
google-search-results = "^2.4.1"
|
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 = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
langchain = "^0.0.202"
|
langchain = "^0.0.229"
|
||||||
openai = "^0.27.8"
|
openai = "^0.27.8"
|
||||||
types-pyyaml = "^6.0.12.8"
|
pandas = "^2.0.0"
|
||||||
pandas = "^1.5.3"
|
|
||||||
chromadb = "^0.3.21"
|
chromadb = "^0.3.21"
|
||||||
huggingface-hub = "^0.13.3"
|
huggingface-hub = "^0.15.0"
|
||||||
rich = "^13.4.2"
|
rich = "^13.4.2"
|
||||||
llama-cpp-python = "~0.1.0"
|
llama-cpp-python = "~0.1.0"
|
||||||
networkx = "^3.1"
|
networkx = "^3.1"
|
||||||
unstructured = "^0.5.11"
|
unstructured = "^0.7.0"
|
||||||
pypdf = "^3.7.1"
|
pypdf = "^3.11.0"
|
||||||
lxml = "^4.9.2"
|
lxml = "^4.9.2"
|
||||||
pysrt = "^1.1.2"
|
pysrt = "^1.1.2"
|
||||||
fake-useragent = "^1.1.3"
|
fake-useragent = "^1.1.3"
|
||||||
|
|
@ -49,35 +48,45 @@ psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^12.0.0"
|
pyarrow = "^12.0.0"
|
||||||
tiktoken = "~0.4.0"
|
tiktoken = "~0.4.0"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
langchain-serve = { version = ">0.0.39", optional = true }
|
langchain-serve = { version = ">0.0.51", optional = true }
|
||||||
qdrant-client = "^1.2.0"
|
qdrant-client = "^1.3.0"
|
||||||
websockets = "^11.0.3"
|
websockets = "^10.3"
|
||||||
weaviate-client = "^3.21.0"
|
weaviate-client = "^3.21.0"
|
||||||
jina = "3.15.2"
|
jina = "3.15.2"
|
||||||
sentence-transformers = "^2.2.2"
|
sentence-transformers = "^2.2.2"
|
||||||
ctransformers = "^0.2.2"
|
ctransformers = "^0.2.10"
|
||||||
cohere = "^4.6.0"
|
cohere = "^4.11.0"
|
||||||
|
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.2.10"
|
anthropic = "^0.3.0"
|
||||||
orjson = "^3.9.1"
|
orjson = "^3.9.1"
|
||||||
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"
|
||||||
|
appdirs = "^1.4.4"
|
||||||
|
pinecone-client = "^2.2.2"
|
||||||
|
supabase = "^1.0.3"
|
||||||
|
pymongo = "^4.4.0"
|
||||||
|
certifi = "^2023.5.7"
|
||||||
|
google-cloud-aiplatform = "^1.26.1"
|
||||||
|
psycopg = "^3.1.9"
|
||||||
|
psycopg-binary = "^3.1.9"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
[tool.poetry.group.dev.dependencies]
|
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
ipykernel = "^6.21.2"
|
ipykernel = "^6.21.2"
|
||||||
mypy = "^1.1.1"
|
mypy = "^1.1.1"
|
||||||
ruff = "^0.0.254"
|
ruff = "^0.0.254"
|
||||||
httpx = "^0.23.3"
|
httpx = "*"
|
||||||
pytest = "^7.2.2"
|
pytest = "^7.2.2"
|
||||||
types-requests = "^2.28.11"
|
types-requests = "^2.28.11"
|
||||||
requests = "^2.28.0"
|
requests = "^2.28.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
pandas-stubs = "^2.0.0.230412"
|
pandas-stubs = "^2.0.0.230412"
|
||||||
types-pillow = "^9.5.0.2"
|
types-pillow = "^9.5.0.2"
|
||||||
|
types-appdirs = "^1.4.3.5"
|
||||||
|
types-pyyaml = "^6.0.12.8"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
|
|
|
||||||
11
render.yaml
Normal file
11
render.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
services:
|
||||||
|
# A Docker web service
|
||||||
|
- type: web
|
||||||
|
name: langflow
|
||||||
|
runtime: docker
|
||||||
|
plan: free
|
||||||
|
dockerfilePath: ./Dockerfile
|
||||||
|
repo: https://github.com/logspace-ai/langflow
|
||||||
|
branch: main
|
||||||
|
healthCheckPath: /health
|
||||||
|
autoDeploy: false
|
||||||
|
|
@ -35,7 +35,7 @@ fi
|
||||||
# Create a firewall rule to allow IAP traffic
|
# Create a firewall rule to allow IAP traffic
|
||||||
firewall_iap_exists=$(gcloud compute firewall-rules list --filter="name=allow-iap" --format="value(name)")
|
firewall_iap_exists=$(gcloud compute firewall-rules list --filter="name=allow-iap" --format="value(name)")
|
||||||
if [[ -z "$firewall_iap_exists" ]]; then
|
if [[ -z "$firewall_iap_exists" ]]; then
|
||||||
gcloud compute firewall-rules create allow-iap --network $VPC_NAME --allow tcp:80,tcp:443,tcp:22,:tcp:3389 --source-ranges 35.235.240.0/20 --direction INGRESS
|
gcloud compute firewall-rules create allow-iap --network $VPC_NAME --allow tcp:80,tcp:443,tcp:22,tcp:3389 --source-ranges 35.235.240.0/20 --direction INGRESS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Define the startup script as a multiline Bash here-doc
|
# Define the startup script as a multiline Bash here-doc
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from importlib import metadata
|
from importlib import metadata
|
||||||
from langflow.cache import cache_manager
|
from langflow.cache import cache_manager # noqa: E402
|
||||||
from langflow.processing.process import load_flow_from_json
|
from langflow.processing.process import load_flow_from_json # noqa: E402
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__version__ = metadata.version(__package__)
|
__version__ = metadata.version(__package__)
|
||||||
|
|
@ -9,4 +9,5 @@ except metadata.PackageNotFoundError:
|
||||||
__version__ = ""
|
__version__ = ""
|
||||||
del metadata # optional, avoids polluting the results of dir(__package__)
|
del metadata # optional, avoids polluting the results of dir(__package__)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["load_flow_from_json", "cache_manager"]
|
__all__ = ["load_flow_from_json", "cache_manager"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from fastapi import FastAPI
|
|
||||||
import httpx
|
import httpx
|
||||||
from multiprocess import Process, cpu_count # type: ignore
|
from multiprocess import Process, cpu_count # type: ignore
|
||||||
import platform
|
import platform
|
||||||
|
|
@ -11,9 +11,7 @@ from rich.panel import Panel
|
||||||
from rich import box
|
from rich import box
|
||||||
from rich import print as rprint
|
from rich import print as rprint
|
||||||
import typer
|
import typer
|
||||||
from fastapi.staticfiles import StaticFiles
|
from langflow.main import setup_app
|
||||||
from fastapi.responses import FileResponse
|
|
||||||
from langflow.main import create_app
|
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.utils.logger import configure, logger
|
from langflow.utils.logger import configure, logger
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
@ -30,17 +28,44 @@ def get_number_of_workers(workers=None):
|
||||||
|
|
||||||
def update_settings(
|
def update_settings(
|
||||||
config: str,
|
config: str,
|
||||||
|
cache: str,
|
||||||
dev: bool = False,
|
dev: bool = False,
|
||||||
database_url: Optional[str] = None,
|
database_url: Optional[str] = None,
|
||||||
remove_api_keys: bool = False,
|
remove_api_keys: bool = False,
|
||||||
):
|
):
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
|
|
||||||
|
# Check for database_url in the environment variables
|
||||||
|
database_url = database_url or os.getenv("langflow_database_url")
|
||||||
|
|
||||||
if config:
|
if config:
|
||||||
settings.update_from_yaml(config, dev=dev)
|
settings.update_from_yaml(config, dev=dev)
|
||||||
if database_url:
|
if database_url:
|
||||||
settings.update_settings(database_url=database_url)
|
settings.update_settings(database_url=database_url)
|
||||||
if remove_api_keys:
|
if remove_api_keys:
|
||||||
settings.update_settings(remove_api_keys=remove_api_keys)
|
settings.update_settings(remove_api_keys=remove_api_keys)
|
||||||
|
if cache:
|
||||||
|
settings.update_settings(cache=cache)
|
||||||
|
|
||||||
|
|
||||||
|
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():
|
||||||
|
|
@ -91,56 +116,75 @@ def serve_on_jcloud():
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def serve(
|
def serve(
|
||||||
host: str = typer.Option("127.0.0.1", help="Host to bind the server to."),
|
host: str = typer.Option(
|
||||||
workers: int = typer.Option(1, help="Number of worker processes."),
|
"127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"
|
||||||
|
),
|
||||||
|
workers: int = typer.Option(
|
||||||
|
1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"
|
||||||
|
),
|
||||||
timeout: int = typer.Option(60, help="Worker timeout in seconds."),
|
timeout: int = typer.Option(60, help="Worker timeout in seconds."),
|
||||||
port: int = typer.Option(7860, help="Port to listen on."),
|
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
|
||||||
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
|
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
|
||||||
# .env file param
|
# .env file param
|
||||||
env_file: Path = typer.Option(
|
env_file: Path = typer.Option(
|
||||||
".env", help="Path to the .env file containing environment variables."
|
".env", help="Path to the .env file containing environment variables."
|
||||||
),
|
),
|
||||||
log_level: str = typer.Option("critical", help="Logging level."),
|
log_level: str = typer.Option(
|
||||||
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
|
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
||||||
|
),
|
||||||
|
log_file: Path = typer.Option(
|
||||||
|
"logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"
|
||||||
|
),
|
||||||
|
cache: str = typer.Option(
|
||||||
|
envvar="LANGFLOW_LANGCHAIN_CACHE",
|
||||||
|
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
|
||||||
|
default="SQLiteCache",
|
||||||
|
),
|
||||||
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(
|
database_url: str = typer.Option(
|
||||||
None,
|
None,
|
||||||
help="Database URL to connect to. If not provided, a local SQLite database will be used.",
|
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.",
|
||||||
|
envvar="LANGFLOW_FRONTEND_PATH",
|
||||||
),
|
),
|
||||||
open_browser: bool = typer.Option(
|
open_browser: bool = typer.Option(
|
||||||
True, help="Open the browser after starting the server."
|
True,
|
||||||
|
help="Open the browser after starting the server.",
|
||||||
|
envvar="LANGFLOW_OPEN_BROWSER",
|
||||||
),
|
),
|
||||||
remove_api_keys: bool = typer.Option(
|
remove_api_keys: bool = typer.Option(
|
||||||
False, help="Remove API keys from the projects saved in the database."
|
False,
|
||||||
|
help="Remove API keys from the projects saved in the database.",
|
||||||
|
envvar="LANGFLOW_REMOVE_API_KEYS",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run the Langflow server.
|
Run the Langflow server.
|
||||||
"""
|
"""
|
||||||
|
# override env variables with .env file
|
||||||
|
if env_file:
|
||||||
|
load_dotenv(env_file, override=True)
|
||||||
|
load_params()
|
||||||
|
|
||||||
if jcloud:
|
if jcloud:
|
||||||
return serve_on_jcloud()
|
return serve_on_jcloud()
|
||||||
|
|
||||||
load_dotenv(env_file)
|
|
||||||
|
|
||||||
configure(log_level=log_level, log_file=log_file)
|
configure(log_level=log_level, log_file=log_file)
|
||||||
update_settings(
|
update_settings(
|
||||||
config, dev=dev, database_url=database_url, remove_api_keys=remove_api_keys
|
config,
|
||||||
|
dev=dev,
|
||||||
|
database_url=database_url,
|
||||||
|
remove_api_keys=remove_api_keys,
|
||||||
|
cache=cache,
|
||||||
)
|
)
|
||||||
# get the directory of the current file
|
# create path object if path is provided
|
||||||
if not path:
|
static_files_dir: Optional[Path] = Path(path) if path else None
|
||||||
frontend_path = Path(__file__).parent
|
app = setup_app(static_files_dir=static_files_dir)
|
||||||
static_files_dir = frontend_path / "frontend"
|
|
||||||
else:
|
|
||||||
static_files_dir = Path(path)
|
|
||||||
|
|
||||||
app = create_app()
|
|
||||||
setup_static_files(app, static_files_dir)
|
|
||||||
# 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)
|
||||||
|
|
@ -188,29 +232,6 @@ def run_on_windows(host, port, log_level, options, app):
|
||||||
run_langflow(host, port, log_level, options, app)
|
run_langflow(host, port, log_level, options, app)
|
||||||
|
|
||||||
|
|
||||||
def setup_static_files(app: FastAPI, static_files_dir: Path):
|
|
||||||
"""
|
|
||||||
Setup the static files directory.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
app (FastAPI): FastAPI app.
|
|
||||||
path (str): Path to the static files directory.
|
|
||||||
"""
|
|
||||||
app.mount(
|
|
||||||
"/",
|
|
||||||
StaticFiles(directory=static_files_dir, html=True),
|
|
||||||
name="static",
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.exception_handler(404)
|
|
||||||
async def custom_404_handler(request, __):
|
|
||||||
path = static_files_dir / "index.html"
|
|
||||||
|
|
||||||
if not path.exists():
|
|
||||||
raise RuntimeError(f"File at path {path} does not exist.")
|
|
||||||
return FileResponse(path)
|
|
||||||
|
|
||||||
|
|
||||||
def is_port_in_use(port, host="localhost"):
|
def is_port_in_use(port, host="localhost"):
|
||||||
"""
|
"""
|
||||||
Check if a port is in use.
|
Check if a port is in use.
|
||||||
|
|
@ -244,7 +265,7 @@ def get_free_port(port):
|
||||||
def print_banner(host, port):
|
def print_banner(host, port):
|
||||||
# console = Console()
|
# console = Console()
|
||||||
|
|
||||||
word = "LangFlow"
|
word = "Langflow"
|
||||||
colors = ["#3300cc"]
|
colors = ["#3300cc"]
|
||||||
|
|
||||||
styled_word = ""
|
styled_word = ""
|
||||||
|
|
@ -291,7 +312,7 @@ def run_langflow(host, port, log_level, options, app):
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.exception(e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,38 @@ def remove_api_keys(flow: dict):
|
||||||
value["value"] = None
|
value["value"] = None
|
||||||
|
|
||||||
return flow
|
return flow
|
||||||
|
|
||||||
|
|
||||||
|
def build_input_keys_response(langchain_object, artifacts):
|
||||||
|
"""Build the input keys response."""
|
||||||
|
|
||||||
|
input_keys_response = {
|
||||||
|
"input_keys": {key: "" for key in langchain_object.input_keys},
|
||||||
|
"memory_keys": [],
|
||||||
|
"handle_keys": artifacts.get("handle_keys", []),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set the input keys values from artifacts
|
||||||
|
for key, value in artifacts.items():
|
||||||
|
if key in input_keys_response["input_keys"]:
|
||||||
|
input_keys_response["input_keys"][key] = value
|
||||||
|
# If the object has memory, that memory will have a memory_variables attribute
|
||||||
|
# memory variables should be removed from the input keys
|
||||||
|
if hasattr(langchain_object, "memory") and hasattr(
|
||||||
|
langchain_object.memory, "memory_variables"
|
||||||
|
):
|
||||||
|
# Remove memory variables from input keys
|
||||||
|
input_keys_response["input_keys"] = {
|
||||||
|
key: value
|
||||||
|
for key, value in input_keys_response["input_keys"].items()
|
||||||
|
if key not in langchain_object.memory.memory_variables
|
||||||
|
}
|
||||||
|
# Add memory variables to memory_keys
|
||||||
|
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
||||||
|
|
||||||
|
if hasattr(langchain_object, "prompt") and hasattr(
|
||||||
|
langchain_object.prompt, "template"
|
||||||
|
):
|
||||||
|
input_keys_response["template"] = langchain_object.prompt.template
|
||||||
|
|
||||||
|
return input_keys_response
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
|
from langchain.prompts import PromptTemplate
|
||||||
|
|
||||||
|
|
||||||
class CacheResponse(BaseModel):
|
class CacheResponse(BaseModel):
|
||||||
|
|
@ -11,8 +13,14 @@ class Code(BaseModel):
|
||||||
code: str
|
code: str
|
||||||
|
|
||||||
|
|
||||||
class Prompt(BaseModel):
|
class FrontendNodeRequest(FrontendNode):
|
||||||
|
template: dict # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class ValidatePromptRequest(BaseModel):
|
||||||
|
name: str
|
||||||
template: str
|
template: str
|
||||||
|
frontend_node: FrontendNodeRequest
|
||||||
|
|
||||||
|
|
||||||
# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
|
# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
|
||||||
|
|
@ -31,6 +39,7 @@ class CodeValidationResponse(BaseModel):
|
||||||
|
|
||||||
class PromptValidationResponse(BaseModel):
|
class PromptValidationResponse(BaseModel):
|
||||||
input_variables: list
|
input_variables: list
|
||||||
|
frontend_node: FrontendNodeRequest
|
||||||
|
|
||||||
|
|
||||||
INVALID_CHARACTERS = {
|
INVALID_CHARACTERS = {
|
||||||
|
|
@ -51,34 +60,93 @@ INVALID_CHARACTERS = {
|
||||||
"}",
|
"}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INVALID_NAMES = {
|
||||||
|
"input_variables",
|
||||||
|
"output_parser",
|
||||||
|
"partial_variables",
|
||||||
|
"template",
|
||||||
|
"template_format",
|
||||||
|
"validate_template",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_prompt(template: str):
|
def validate_prompt(template: str):
|
||||||
input_variables = extract_input_variables_from_prompt(template)
|
input_variables = extract_input_variables_from_prompt(template)
|
||||||
|
|
||||||
# Check if there are invalid characters in the input_variables
|
# Check if there are invalid characters in the input_variables
|
||||||
input_variables = check_input_variables(input_variables)
|
input_variables = check_input_variables(input_variables)
|
||||||
|
if any(var in INVALID_NAMES for var in input_variables):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
|
||||||
|
)
|
||||||
|
|
||||||
return PromptValidationResponse(input_variables=input_variables)
|
try:
|
||||||
|
PromptTemplate(template=template, input_variables=input_variables)
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError(str(exc)) from exc
|
||||||
|
|
||||||
|
return input_variables
|
||||||
|
|
||||||
|
|
||||||
def check_input_variables(input_variables: list):
|
def check_input_variables(input_variables: list):
|
||||||
invalid_chars = []
|
invalid_chars = []
|
||||||
fixed_variables = []
|
fixed_variables = []
|
||||||
|
wrong_variables = []
|
||||||
|
empty_variables = []
|
||||||
for variable in input_variables:
|
for variable in input_variables:
|
||||||
new_var = variable
|
new_var = variable
|
||||||
for char in INVALID_CHARACTERS:
|
|
||||||
if char in variable:
|
# if variable is empty, then we should add that to the wrong variables
|
||||||
invalid_chars.append(char)
|
if not variable:
|
||||||
new_var = new_var.replace(char, "")
|
empty_variables.append(variable)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if variable starts with a number we should add that to the invalid chars
|
||||||
|
# and wrong variables
|
||||||
|
if variable[0].isdigit():
|
||||||
|
invalid_chars.append(variable[0])
|
||||||
|
new_var = new_var.replace(variable[0], "")
|
||||||
|
wrong_variables.append(variable)
|
||||||
|
else:
|
||||||
|
for char in INVALID_CHARACTERS:
|
||||||
|
if char in variable:
|
||||||
|
invalid_chars.append(char)
|
||||||
|
new_var = new_var.replace(char, "")
|
||||||
|
wrong_variables.append(variable)
|
||||||
fixed_variables.append(new_var)
|
fixed_variables.append(new_var)
|
||||||
if new_var != variable:
|
|
||||||
input_variables.remove(variable)
|
|
||||||
input_variables.append(new_var)
|
|
||||||
# If any of the input_variables is not in the fixed_variables, then it means that
|
# If any of the input_variables is not in the fixed_variables, then it means that
|
||||||
# there are invalid characters in the input_variables
|
# there are invalid characters in the input_variables
|
||||||
if any(var not in fixed_variables for var in input_variables):
|
|
||||||
raise ValueError(
|
|
||||||
f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if any(var not in fixed_variables for var in input_variables):
|
||||||
|
error_message = build_error_message(
|
||||||
|
input_variables,
|
||||||
|
invalid_chars,
|
||||||
|
wrong_variables,
|
||||||
|
fixed_variables,
|
||||||
|
empty_variables,
|
||||||
|
)
|
||||||
|
raise ValueError(error_message)
|
||||||
return input_variables
|
return input_variables
|
||||||
|
|
||||||
|
|
||||||
|
def build_error_message(
|
||||||
|
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
|
||||||
|
):
|
||||||
|
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
|
||||||
|
error_string = f"Invalid input variables: {input_variables_str}. "
|
||||||
|
|
||||||
|
if wrong_variables and invalid_chars:
|
||||||
|
# fix the wrong variables replacing invalid chars and find them in the fixed variables
|
||||||
|
error_string_vars = "You can fix them by replacing the invalid characters: "
|
||||||
|
wvars = wrong_variables.copy()
|
||||||
|
for i, wrong_var in enumerate(wvars):
|
||||||
|
for char in invalid_chars:
|
||||||
|
wrong_var = wrong_var.replace(char, "")
|
||||||
|
if wrong_var in fixed_variables:
|
||||||
|
error_string_vars += f"'{wrong_variables[i]}' -> '{wrong_var}'"
|
||||||
|
error_string += error_string_vars
|
||||||
|
elif empty_variables:
|
||||||
|
error_string += f" There are {len(empty_variables)} empty variable{'s' if len(empty_variables) > 1 else ''}."
|
||||||
|
elif len(set(fixed_variables)) != len(fixed_variables):
|
||||||
|
error_string += "There are duplicate variables."
|
||||||
|
return error_string
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,132 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
||||||
|
|
||||||
from langflow.api.v1.schemas import ChatResponse
|
from langflow.api.v1.schemas import ChatResponse
|
||||||
|
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
from fastapi import WebSocket
|
||||||
|
|
||||||
|
|
||||||
|
from langchain.schema import AgentAction, LLMResult, AgentFinish
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
||||||
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
"""Callback handler for streaming LLM responses."""
|
"""Callback handler for streaming LLM responses."""
|
||||||
|
|
||||||
def __init__(self, websocket):
|
def __init__(self, websocket: WebSocket):
|
||||||
self.websocket = websocket
|
self.websocket = websocket
|
||||||
|
|
||||||
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.dict())
|
||||||
|
|
||||||
|
async def on_llm_start(
|
||||||
|
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
|
||||||
|
) -> Any:
|
||||||
|
"""Run when LLM starts running."""
|
||||||
|
|
||||||
|
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
|
||||||
|
"""Run when LLM ends running."""
|
||||||
|
|
||||||
|
async def on_llm_error(
|
||||||
|
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
|
||||||
|
) -> Any:
|
||||||
|
"""Run when LLM errors."""
|
||||||
|
|
||||||
|
async def on_chain_start(
|
||||||
|
self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
|
||||||
|
) -> Any:
|
||||||
|
"""Run when chain starts running."""
|
||||||
|
|
||||||
|
async def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
|
||||||
|
"""Run when chain ends running."""
|
||||||
|
|
||||||
|
async def on_chain_error(
|
||||||
|
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
|
||||||
|
) -> Any:
|
||||||
|
"""Run when chain errors."""
|
||||||
|
|
||||||
|
async def on_tool_start(
|
||||||
|
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
|
||||||
|
) -> Any:
|
||||||
|
"""Run when tool starts running."""
|
||||||
|
resp = ChatResponse(
|
||||||
|
message="",
|
||||||
|
type="stream",
|
||||||
|
intermediate_steps=f"Tool input: {input_str}",
|
||||||
|
)
|
||||||
|
await self.websocket.send_json(resp.dict())
|
||||||
|
|
||||||
|
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
|
||||||
|
"""Run when tool ends running."""
|
||||||
|
observation_prefix = kwargs.get("observation_prefix", "Tool output: ")
|
||||||
|
split_output = output.split()
|
||||||
|
first_word = split_output[0]
|
||||||
|
rest_of_output = split_output[1:]
|
||||||
|
# Create a formatted message.
|
||||||
|
intermediate_steps = f"{observation_prefix}{first_word}"
|
||||||
|
|
||||||
|
# Create a ChatResponse instance.
|
||||||
|
resp = ChatResponse(
|
||||||
|
message="",
|
||||||
|
type="stream",
|
||||||
|
intermediate_steps=intermediate_steps,
|
||||||
|
)
|
||||||
|
rest_of_resps = [
|
||||||
|
ChatResponse(
|
||||||
|
message="",
|
||||||
|
type="stream",
|
||||||
|
intermediate_steps=f"{word}",
|
||||||
|
)
|
||||||
|
for word in rest_of_output
|
||||||
|
]
|
||||||
|
resps = [resp] + rest_of_resps
|
||||||
|
# Try to send the response, handle potential errors.
|
||||||
|
|
||||||
|
try:
|
||||||
|
# This is to emulate the stream of tokens
|
||||||
|
for resp in resps:
|
||||||
|
await self.websocket.send_json(resp.dict())
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
|
async def on_tool_error(
|
||||||
|
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
|
||||||
|
) -> Any:
|
||||||
|
"""Run when tool errors."""
|
||||||
|
|
||||||
|
async def on_text(self, text: str, **kwargs: Any) -> Any:
|
||||||
|
"""Run on arbitrary text."""
|
||||||
|
# This runs when first sending the prompt
|
||||||
|
# to the LLM, adding it will send the final prompt
|
||||||
|
# to the frontend
|
||||||
|
|
||||||
|
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
||||||
|
log = f"Thought: {action.log}"
|
||||||
|
# if there are line breaks, split them and send them
|
||||||
|
# as separate messages
|
||||||
|
if "\n" in log:
|
||||||
|
logs = log.split("\n")
|
||||||
|
for log in logs:
|
||||||
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
|
await self.websocket.send_json(resp.dict())
|
||||||
|
else:
|
||||||
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
|
await self.websocket.send_json(resp.dict())
|
||||||
|
|
||||||
|
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
|
||||||
|
"""Run on agent end."""
|
||||||
|
resp = ChatResponse(
|
||||||
|
message="",
|
||||||
|
type="stream",
|
||||||
|
intermediate_steps=finish.log,
|
||||||
|
)
|
||||||
|
await self.websocket.send_json(resp.dict())
|
||||||
|
|
||||||
|
|
||||||
class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
||||||
"""Callback handler for streaming LLM responses."""
|
"""Callback handler for streaming LLM responses."""
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import json
|
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketException, status
|
||||||
from fastapi import (
|
|
||||||
APIRouter,
|
|
||||||
HTTPException,
|
|
||||||
WebSocket,
|
|
||||||
WebSocketException,
|
|
||||||
status,
|
|
||||||
)
|
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from langflow.api.v1.schemas import BuiltResponse, InitResponse
|
from langflow.api.utils import build_input_keys_response
|
||||||
|
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
||||||
|
|
||||||
from langflow.chat.manager import ChatManager
|
from langflow.chat.manager import ChatManager
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
|
|
@ -26,22 +20,39 @@ async def chat(client_id: str, websocket: WebSocket):
|
||||||
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:
|
||||||
|
# We accept the connection but close it immediately
|
||||||
|
# if the flow is not built yet
|
||||||
|
await websocket.accept()
|
||||||
message = "Please, build the flow before sending messages"
|
message = "Please, build the flow before sending messages"
|
||||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason=message)
|
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=message)
|
||||||
except WebSocketException as exc:
|
except WebSocketException as exc:
|
||||||
logger.error(exc)
|
logger.error(exc)
|
||||||
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
|
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
|
||||||
|
|
||||||
|
|
||||||
@router.post("/build/init", response_model=InitResponse, status_code=201)
|
@router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201)
|
||||||
async def init_build(graph_data: dict):
|
async def init_build(graph_data: dict, flow_id: str):
|
||||||
"""Initialize the build by storing graph data and returning a unique session ID."""
|
"""Initialize the build by storing graph data and returning a unique session ID."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flow_id = graph_data.get("id")
|
|
||||||
if flow_id is None:
|
if flow_id is None:
|
||||||
raise ValueError("No ID provided")
|
raise ValueError("No ID provided")
|
||||||
flow_data_store[flow_id] = graph_data
|
# Check if already building
|
||||||
|
if (
|
||||||
|
flow_id in flow_data_store
|
||||||
|
and flow_data_store[flow_id]["status"] == BuildStatus.IN_PROGRESS
|
||||||
|
):
|
||||||
|
return InitResponse(flowId=flow_id)
|
||||||
|
|
||||||
|
# Delete from cache if already exists
|
||||||
|
if flow_id in chat_manager.in_memory_cache:
|
||||||
|
with chat_manager.in_memory_cache._lock:
|
||||||
|
chat_manager.in_memory_cache.delete(flow_id)
|
||||||
|
logger.debug(f"Deleted flow {flow_id} from cache")
|
||||||
|
flow_data_store[flow_id] = {
|
||||||
|
"graph_data": graph_data,
|
||||||
|
"status": BuildStatus.STARTED,
|
||||||
|
}
|
||||||
|
|
||||||
return InitResponse(flowId=flow_id)
|
return InitResponse(flowId=flow_id)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
@ -53,8 +64,9 @@ async def init_build(graph_data: dict):
|
||||||
async def build_status(flow_id: str):
|
async def build_status(flow_id: str):
|
||||||
"""Check the flow_id is in the flow_data_store."""
|
"""Check the flow_id is in the flow_data_store."""
|
||||||
try:
|
try:
|
||||||
built = flow_id in flow_data_store and not isinstance(
|
built = (
|
||||||
flow_data_store[flow_id], dict
|
flow_id in flow_data_store
|
||||||
|
and flow_data_store[flow_id]["status"] == BuildStatus.SUCCESS
|
||||||
)
|
)
|
||||||
|
|
||||||
return BuiltResponse(
|
return BuiltResponse(
|
||||||
|
|
@ -71,55 +83,95 @@ async def stream_build(flow_id: str):
|
||||||
"""Stream the build process based on stored flow data."""
|
"""Stream the build process based on stored flow data."""
|
||||||
|
|
||||||
async def event_stream(flow_id):
|
async def event_stream(flow_id):
|
||||||
final_response = json.dumps({"end_of_stream": True})
|
final_response = {"end_of_stream": True}
|
||||||
|
artifacts = {}
|
||||||
try:
|
try:
|
||||||
if flow_id not in flow_data_store:
|
if flow_id not in flow_data_store:
|
||||||
error_message = "Invalid session ID"
|
error_message = "Invalid session ID"
|
||||||
yield f"data: {json.dumps({'error': error_message})}\n\n"
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
return
|
return
|
||||||
|
|
||||||
graph_data = flow_data_store[flow_id].get("data")
|
if flow_data_store[flow_id].get("status") == BuildStatus.IN_PROGRESS:
|
||||||
|
error_message = "Already building"
|
||||||
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
|
return
|
||||||
|
|
||||||
|
graph_data = flow_data_store[flow_id].get("graph_data")
|
||||||
|
|
||||||
if not graph_data:
|
if not graph_data:
|
||||||
error_message = "No data provided"
|
error_message = "No data provided"
|
||||||
yield f"data: {json.dumps({'error': error_message})}\n\n"
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug("Building langchain object")
|
logger.debug("Building langchain object")
|
||||||
graph = Graph.from_payload(graph_data)
|
try:
|
||||||
|
# Some error could happen when building the graph
|
||||||
|
graph = Graph.from_payload(graph_data)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
|
error_message = str(exc)
|
||||||
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
|
return
|
||||||
|
|
||||||
number_of_nodes = len(graph.nodes)
|
number_of_nodes = len(graph.nodes)
|
||||||
|
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
|
||||||
|
|
||||||
for i, vertex in enumerate(graph.generator_build(), 1):
|
for i, vertex in enumerate(graph.generator_build(), 1):
|
||||||
try:
|
try:
|
||||||
log_dict = {
|
log_dict = {
|
||||||
"log": f"Building node {vertex.vertex_type}",
|
"log": f"Building node {vertex.vertex_type}",
|
||||||
"progress": round(i / number_of_nodes, 2),
|
|
||||||
}
|
}
|
||||||
yield f"data: {json.dumps(log_dict)}\n\n"
|
yield str(StreamData(event="log", data=log_dict))
|
||||||
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 {params[:50]}{'...' if len(params) > 50 else ''}"
|
f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}"
|
||||||
)
|
)
|
||||||
|
if vertex.artifacts:
|
||||||
|
# The artifacts will be prompt variables
|
||||||
|
# passed to build_input_keys_response
|
||||||
|
# to set the input_keys values
|
||||||
|
artifacts.update(vertex.artifacts)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
params = str(exc)
|
params = str(exc)
|
||||||
valid = False
|
valid = False
|
||||||
|
flow_data_store[flow_id]["status"] = BuildStatus.FAILURE
|
||||||
|
|
||||||
response = json.dumps(
|
response = {
|
||||||
{
|
"valid": valid,
|
||||||
"valid": valid,
|
"params": params,
|
||||||
"params": params,
|
"id": vertex.id,
|
||||||
"id": vertex.id,
|
"progress": round(i / number_of_nodes, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
yield str(StreamData(event="message", data=response))
|
||||||
|
|
||||||
|
langchain_object = graph.build()
|
||||||
|
# Now we need to check the input_keys to send them to the client
|
||||||
|
if hasattr(langchain_object, "input_keys"):
|
||||||
|
input_keys_response = build_input_keys_response(
|
||||||
|
langchain_object, artifacts
|
||||||
)
|
)
|
||||||
yield f"data: {response}\n\n"
|
else:
|
||||||
|
input_keys_response = {
|
||||||
|
"input_keys": {},
|
||||||
|
"memory_keys": [],
|
||||||
|
"handle_keys": [],
|
||||||
|
}
|
||||||
|
yield str(StreamData(event="message", data=input_keys_response))
|
||||||
|
|
||||||
chat_manager.set_cache(flow_id, graph.build())
|
chat_manager.set_cache(flow_id, langchain_object)
|
||||||
|
# We need to reset the chat history
|
||||||
|
chat_manager.chat_history.empty_history(flow_id)
|
||||||
|
flow_data_store[flow_id]["status"] = BuildStatus.SUCCESS
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
logger.error("Error while building the flow: %s", exc)
|
logger.error("Error while building the flow: %s", exc)
|
||||||
yield f"error: {json.dumps({'error': str(exc)})}\n\n"
|
flow_data_store[flow_id]["status"] = BuildStatus.FAILURE
|
||||||
|
yield str(StreamData(event="error", data={"error": str(exc)}))
|
||||||
finally:
|
finally:
|
||||||
yield f"data: {final_response}\n\n"
|
yield str(StreamData(event="message", data=final_response))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return StreamingResponse(event_stream(flow_id), media_type="text/event-stream")
|
return StreamingResponse(event_stream(flow_id), media_type="text/event-stream")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow.cache.utils import save_uploaded_file
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.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.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||||
|
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
PredictRequest,
|
ProcessResponse,
|
||||||
PredictResponse,
|
UploadFileResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.interface.types import build_langchain_types_dict
|
from langflow.interface.types import langchain_types_dict
|
||||||
from langflow.database.base import get_session
|
from langflow.database.base import get_session
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
|
@ -19,17 +21,20 @@ router = APIRouter(tags=["Base"])
|
||||||
|
|
||||||
@router.get("/all")
|
@router.get("/all")
|
||||||
def get_all():
|
def get_all():
|
||||||
return build_langchain_types_dict()
|
return langchain_types_dict
|
||||||
|
|
||||||
|
|
||||||
@router.post("/predict/{flow_id}", response_model=PredictResponse)
|
# For backwards compatibility we will keep the old endpoint
|
||||||
async def predict_flow(
|
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
|
||||||
predict_request: PredictRequest,
|
@router.post("/process/{flow_id}", response_model=ProcessResponse)
|
||||||
|
async def process_flow(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
|
inputs: Optional[dict] = None,
|
||||||
|
tweaks: Optional[dict] = None,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Endpoint to process a message using the flow passed in the bearer token.
|
Endpoint to process an input with a given flow_id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -40,15 +45,14 @@ async def predict_flow(
|
||||||
if flow.data is None:
|
if flow.data is None:
|
||||||
raise ValueError(f"Flow {flow_id} has no data")
|
raise ValueError(f"Flow {flow_id} has no data")
|
||||||
graph_data = flow.data
|
graph_data = flow.data
|
||||||
if predict_request.tweaks:
|
if tweaks:
|
||||||
try:
|
try:
|
||||||
graph_data = process_tweaks(graph_data, predict_request.tweaks)
|
graph_data = process_tweaks(graph_data, tweaks)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error processing tweaks: {exc}")
|
logger.error(f"Error processing tweaks: {exc}")
|
||||||
response = process_graph_cached(graph_data, predict_request.message)
|
response = process_graph_cached(graph_data, inputs)
|
||||||
return PredictResponse(
|
return ProcessResponse(
|
||||||
result=response.get("result", ""),
|
result=response,
|
||||||
intermediate_steps=response.get("thought", ""),
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
|
|
@ -56,6 +60,21 @@ async def predict_flow(
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload/{flow_id}", response_model=UploadFileResponse, status_code=201)
|
||||||
|
async def create_upload_file(file: UploadFile, flow_id: str):
|
||||||
|
# Cache file
|
||||||
|
try:
|
||||||
|
file_path = save_uploaded_file(file.file, folder_name=flow_id)
|
||||||
|
|
||||||
|
return UploadFileResponse(
|
||||||
|
flowId=flow_id,
|
||||||
|
file_path=file_path,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error saving file: {exc}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
# get endpoint to return version of langflow
|
# get endpoint to return version of langflow
|
||||||
@router.get("/version")
|
@router.get("/version")
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
|
from enum import Enum
|
||||||
|
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.database.models.flow import FlowCreate, FlowRead
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class BuildStatus(Enum):
|
||||||
|
"""Status of the build."""
|
||||||
|
|
||||||
|
SUCCESS = "success"
|
||||||
|
FAILURE = "failure"
|
||||||
|
STARTED = "started"
|
||||||
|
IN_PROGRESS = "in_progress"
|
||||||
|
|
||||||
|
|
||||||
class GraphData(BaseModel):
|
class GraphData(BaseModel):
|
||||||
|
|
@ -11,7 +23,7 @@ class GraphData(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ExportedFlow(BaseModel):
|
class ExportedFlow(BaseModel):
|
||||||
"""Exported flow from LangFlow."""
|
"""Exported flow from Langflow."""
|
||||||
|
|
||||||
description: str
|
description: str
|
||||||
name: str
|
name: str
|
||||||
|
|
@ -19,41 +31,29 @@ class ExportedFlow(BaseModel):
|
||||||
data: GraphData
|
data: GraphData
|
||||||
|
|
||||||
|
|
||||||
class PredictRequest(BaseModel):
|
class InputRequest(BaseModel):
|
||||||
"""Predict request schema."""
|
input: dict
|
||||||
|
|
||||||
message: str
|
|
||||||
|
class TweaksRequest(BaseModel):
|
||||||
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
||||||
|
|
||||||
class Config:
|
|
||||||
schema_extra = {
|
class UpdateTemplateRequest(BaseModel):
|
||||||
"example": {
|
template: dict
|
||||||
"message": "Hello, how are you?",
|
|
||||||
"tweaks": {
|
|
||||||
"dndnode_986363f0-4677-4035-9f38-74b94af5dd78": {
|
|
||||||
"name": "A tool name",
|
|
||||||
"description": "A tool description",
|
|
||||||
},
|
|
||||||
"dndnode_986363f0-4677-4035-9f38-74b94af57378": {
|
|
||||||
"template": "A {template}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PredictResponse(BaseModel):
|
class ProcessResponse(BaseModel):
|
||||||
"""Predict response schema."""
|
"""Process response schema."""
|
||||||
|
|
||||||
result: str
|
result: dict
|
||||||
intermediate_steps: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
"""Chat message schema."""
|
"""Chat message schema."""
|
||||||
|
|
||||||
is_bot: bool = False
|
is_bot: bool = False
|
||||||
message: Union[str, None] = None
|
message: Union[str, None, dict] = None
|
||||||
type: str = "human"
|
type: str = "human"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -101,3 +101,18 @@ class InitResponse(BaseModel):
|
||||||
|
|
||||||
class BuiltResponse(BaseModel):
|
class BuiltResponse(BaseModel):
|
||||||
built: bool
|
built: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UploadFileResponse(BaseModel):
|
||||||
|
"""Upload file response schema."""
|
||||||
|
|
||||||
|
flowId: str
|
||||||
|
file_path: Path
|
||||||
|
|
||||||
|
|
||||||
|
class StreamData(BaseModel):
|
||||||
|
event: str
|
||||||
|
data: dict
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n"
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,13 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from langflow.api.v1.base import (
|
from langflow.api.v1.base import (
|
||||||
Code,
|
Code,
|
||||||
CodeValidationResponse,
|
CodeValidationResponse,
|
||||||
Prompt,
|
ValidatePromptRequest,
|
||||||
PromptValidationResponse,
|
PromptValidationResponse,
|
||||||
validate_prompt,
|
validate_prompt,
|
||||||
)
|
)
|
||||||
from langflow.graph.vertex.types import VectorStoreVertex
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.graph import Graph
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.validate import validate_code
|
from langflow.utils.validate import validate_code
|
||||||
|
|
||||||
|
|
@ -31,27 +28,100 @@ def post_validate_code(code: Code):
|
||||||
|
|
||||||
|
|
||||||
@router.post("/prompt", status_code=200, response_model=PromptValidationResponse)
|
@router.post("/prompt", status_code=200, response_model=PromptValidationResponse)
|
||||||
def post_validate_prompt(prompt: Prompt):
|
def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
try:
|
try:
|
||||||
return validate_prompt(prompt.template)
|
input_variables = validate_prompt(prompt_request.template)
|
||||||
|
|
||||||
|
old_custom_fields = get_old_custom_fields(prompt_request)
|
||||||
|
|
||||||
|
add_new_variables_to_template(input_variables, prompt_request)
|
||||||
|
|
||||||
|
remove_old_variables_from_template(
|
||||||
|
old_custom_fields, input_variables, prompt_request
|
||||||
|
)
|
||||||
|
|
||||||
|
update_input_variables_field(input_variables, prompt_request)
|
||||||
|
|
||||||
|
return PromptValidationResponse(
|
||||||
|
input_variables=input_variables,
|
||||||
|
frontend_node=prompt_request.frontend_node,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
# validate node
|
def get_old_custom_fields(prompt_request):
|
||||||
@router.post("/node/{node_id}", status_code=200)
|
|
||||||
def post_validate_node(node_id: str, data: dict):
|
|
||||||
try:
|
try:
|
||||||
# build graph
|
old_custom_fields = prompt_request.frontend_node.custom_fields[
|
||||||
graph = Graph.from_payload(data)
|
prompt_request.name
|
||||||
# validate node
|
].copy()
|
||||||
node = graph.get_node(node_id)
|
except KeyError:
|
||||||
if node is None:
|
old_custom_fields = []
|
||||||
raise ValueError(f"Node {node_id} not found")
|
prompt_request.frontend_node.custom_fields[prompt_request.name] = []
|
||||||
if not isinstance(node, VectorStoreVertex):
|
return old_custom_fields
|
||||||
node.build()
|
|
||||||
return json.dumps({"valid": True, "params": str(node._built_object_repr())})
|
|
||||||
except Exception as e:
|
def add_new_variables_to_template(input_variables, prompt_request):
|
||||||
logger.exception(e)
|
for variable in input_variables:
|
||||||
return json.dumps({"valid": False, "params": str(e)})
|
try:
|
||||||
|
template_field = TemplateField(
|
||||||
|
name=variable,
|
||||||
|
display_name=variable,
|
||||||
|
field_type="str",
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
multiline=True,
|
||||||
|
input_types=["Document", "BaseOutputParser"],
|
||||||
|
value="", # Set the value to empty string
|
||||||
|
)
|
||||||
|
if variable in prompt_request.frontend_node.template:
|
||||||
|
# Set the new field with the old value
|
||||||
|
template_field.value = prompt_request.frontend_node.template[variable][
|
||||||
|
"value"
|
||||||
|
]
|
||||||
|
|
||||||
|
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
||||||
|
|
||||||
|
# Check if variable is not already in the list before appending
|
||||||
|
if (
|
||||||
|
variable
|
||||||
|
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
||||||
|
):
|
||||||
|
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
|
||||||
|
variable
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
|
def remove_old_variables_from_template(
|
||||||
|
old_custom_fields, input_variables, prompt_request
|
||||||
|
):
|
||||||
|
for variable in old_custom_fields:
|
||||||
|
if variable not in input_variables:
|
||||||
|
try:
|
||||||
|
# Remove the variable from custom_fields associated with the given name
|
||||||
|
if (
|
||||||
|
variable
|
||||||
|
in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
||||||
|
):
|
||||||
|
prompt_request.frontend_node.custom_fields[
|
||||||
|
prompt_request.name
|
||||||
|
].remove(variable)
|
||||||
|
|
||||||
|
# Remove the variable from the template
|
||||||
|
prompt_request.frontend_node.template.pop(variable, None)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
|
def update_input_variables_field(input_variables, prompt_request):
|
||||||
|
if "input_variables" in prompt_request.frontend_node.template:
|
||||||
|
prompt_request.frontend_node.template["input_variables"][
|
||||||
|
"value"
|
||||||
|
] = input_variables
|
||||||
|
|
|
||||||
3
src/backend/langflow/cache/base.py
vendored
3
src/backend/langflow/cache/base.py
vendored
|
|
@ -62,9 +62,6 @@ class BaseCache(abc.ABC):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: The key of the item to retrieve.
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
Returns:
|
|
||||||
The value associated with the key, or None if the key is not found.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|
|
||||||
51
src/backend/langflow/cache/utils.py
vendored
51
src/backend/langflow/cache/utils.py
vendored
|
|
@ -8,15 +8,17 @@ import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
from appdirs import user_cache_dir
|
||||||
|
|
||||||
CACHE: Dict[str, Any] = {}
|
CACHE: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
CACHE_DIR = user_cache_dir("langflow", "langflow")
|
||||||
|
|
||||||
|
|
||||||
def create_cache_folder(func):
|
def create_cache_folder(func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
|
|
||||||
# Create the destination folder if it doesn't exist
|
# Create the destination folder if it doesn't exist
|
||||||
os.makedirs(cache_path, exist_ok=True)
|
os.makedirs(cache_path, exist_ok=True)
|
||||||
|
|
@ -118,7 +120,7 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
raise ValueError(f"File {file_name} is not accepted")
|
raise ValueError(f"File {file_name} is not accepted")
|
||||||
|
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
if not content:
|
if not content:
|
||||||
raise ValueError("Please, reload the file in the loader.")
|
raise ValueError("Please, reload the file in the loader.")
|
||||||
data = content.split(",")[1]
|
data = content.split(",")[1]
|
||||||
|
|
@ -132,3 +134,46 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
file.write(decoded_bytes)
|
file.write(decoded_bytes)
|
||||||
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
|
@create_cache_folder
|
||||||
|
def save_uploaded_file(file, folder_name):
|
||||||
|
"""
|
||||||
|
Save an uploaded file to the specified folder with a hash of its content as the file name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file: The uploaded file object.
|
||||||
|
folder_name: The name of the folder to save the file in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path to the saved file.
|
||||||
|
"""
|
||||||
|
cache_path = Path(CACHE_DIR)
|
||||||
|
folder_path = cache_path / folder_name
|
||||||
|
|
||||||
|
# Create the folder if it doesn't exist
|
||||||
|
if not folder_path.exists():
|
||||||
|
folder_path.mkdir()
|
||||||
|
|
||||||
|
# Create a hash of the file content
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
# Iterate over the uploaded file in small chunks to conserve memory
|
||||||
|
while chunk := file.read(8192): # Read 8KB at a time (adjust as needed)
|
||||||
|
sha256_hash.update(chunk)
|
||||||
|
|
||||||
|
# Use the hex digest of the hash as the file name
|
||||||
|
hex_dig = sha256_hash.hexdigest()
|
||||||
|
file_name = hex_dig
|
||||||
|
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
|
||||||
|
# Save the file with the hash as its name
|
||||||
|
file_path = folder_path / file_name
|
||||||
|
with open(file_path, "wb") as new_file:
|
||||||
|
while chunk := file.read(8192):
|
||||||
|
new_file.write(chunk)
|
||||||
|
|
||||||
|
return file_path
|
||||||
|
|
|
||||||
2
src/backend/langflow/chat/config.py
Normal file
2
src/backend/langflow/chat/config.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
class ChatConfig:
|
||||||
|
streaming: bool = True
|
||||||
|
|
@ -104,16 +104,22 @@ class ChatManager:
|
||||||
|
|
||||||
async def close_connection(self, client_id: str, code: int, reason: str):
|
async def close_connection(self, client_id: str, code: int, reason: str):
|
||||||
if websocket := self.active_connections[client_id]:
|
if websocket := self.active_connections[client_id]:
|
||||||
await websocket.close(code=code, reason=reason)
|
try:
|
||||||
self.disconnect(client_id)
|
await websocket.close(code=code, reason=reason)
|
||||||
|
self.disconnect(client_id)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
# This is to catch the following error:
|
||||||
|
# Unexpected ASGI message 'websocket.close', after sending 'websocket.close'
|
||||||
|
if "after sending" in str(exc):
|
||||||
|
logger.error(exc)
|
||||||
|
|
||||||
async def process_message(
|
async def process_message(
|
||||||
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_message = payload.pop("message", "")
|
chat_inputs = payload.pop("inputs", "")
|
||||||
chat_message = ChatMessage(message=chat_message)
|
chat_inputs = ChatMessage(message=chat_inputs)
|
||||||
self.chat_history.add_message(client_id, chat_message)
|
self.chat_history.add_message(client_id, chat_inputs)
|
||||||
|
|
||||||
# graph_data = payload
|
# graph_data = payload
|
||||||
start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
|
start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
|
||||||
|
|
@ -126,7 +132,7 @@ class ChatManager:
|
||||||
|
|
||||||
result, intermediate_steps = await process_graph(
|
result, intermediate_steps = await process_graph(
|
||||||
langchain_object=langchain_object,
|
langchain_object=langchain_object,
|
||||||
chat_message=chat_message,
|
chat_inputs=chat_inputs,
|
||||||
websocket=self.active_connections[client_id],
|
websocket=self.active_connections[client_id],
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -144,6 +150,8 @@ class ChatManager:
|
||||||
if isinstance(msg, FileResponse):
|
if isinstance(msg, FileResponse):
|
||||||
if msg.data_type == "image":
|
if msg.data_type == "image":
|
||||||
# Base64 encode the image
|
# Base64 encode the image
|
||||||
|
if isinstance(msg.data, str):
|
||||||
|
continue
|
||||||
msg.data = pil_to_base64(msg.data)
|
msg.data = pil_to_base64(msg.data)
|
||||||
file_responses.append(msg)
|
file_responses.append(msg)
|
||||||
if msg.type == "start":
|
if msg.type == "start":
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from langflow.utils.logger import logger
|
||||||
|
|
||||||
async def process_graph(
|
async def process_graph(
|
||||||
langchain_object,
|
langchain_object,
|
||||||
chat_message: ChatMessage,
|
chat_inputs: ChatMessage,
|
||||||
websocket: WebSocket,
|
websocket: WebSocket,
|
||||||
):
|
):
|
||||||
langchain_object = try_setting_streaming_options(langchain_object, websocket)
|
langchain_object = try_setting_streaming_options(langchain_object, websocket)
|
||||||
|
|
@ -21,9 +21,13 @@ async def process_graph(
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
try:
|
try:
|
||||||
|
if not chat_inputs.message:
|
||||||
|
logger.debug("No message provided")
|
||||||
|
raise ValueError("No message provided")
|
||||||
|
|
||||||
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(
|
||||||
langchain_object, chat_message.message or "", websocket=websocket
|
langchain_object, chat_inputs.message, websocket=websocket
|
||||||
)
|
)
|
||||||
logger.debug("Generated result and intermediate_steps")
|
logger.debug("Generated result and intermediate_steps")
|
||||||
return result, intermediate_steps
|
return result, intermediate_steps
|
||||||
|
|
|
||||||
|
|
@ -1,136 +1,292 @@
|
||||||
---
|
|
||||||
agents:
|
agents:
|
||||||
- ZeroShotAgent
|
ZeroShotAgent:
|
||||||
- JsonAgent
|
documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent"
|
||||||
- CSVAgent
|
JsonAgent:
|
||||||
- AgentInitializer
|
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/openapi"
|
||||||
- VectorStoreAgent
|
CSVAgent:
|
||||||
- VectorStoreRouterAgent
|
documentation: "https://python.langchain.com/docs/modules/agents/toolkits/csv"
|
||||||
- SQLAgent
|
AgentInitializer:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/agents/agent_types/"
|
||||||
|
VectorStoreAgent:
|
||||||
|
documentation: ""
|
||||||
|
VectorStoreRouterAgent:
|
||||||
|
documentation: ""
|
||||||
|
SQLAgent:
|
||||||
|
documentation: ""
|
||||||
chains:
|
chains:
|
||||||
- LLMChain
|
LLMChain:
|
||||||
- LLMMathChain
|
documentation: "https://python.langchain.com/docs/modules/chains/foundational/llm_chain"
|
||||||
- LLMCheckerChain
|
LLMMathChain:
|
||||||
- ConversationChain
|
documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_math"
|
||||||
- SeriesCharacterChain
|
LLMCheckerChain:
|
||||||
- MidJourneyPromptChain
|
documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
|
||||||
- TimeTravelGuideChain
|
ConversationChain:
|
||||||
- SQLDatabaseChain
|
documentation: ""
|
||||||
- RetrievalQA
|
SeriesCharacterChain:
|
||||||
- RetrievalQAWithSourcesChain
|
documentation: ""
|
||||||
- ConversationalRetrievalChain
|
MidJourneyPromptChain:
|
||||||
- CombineDocsChain
|
documentation: ""
|
||||||
|
TimeTravelGuideChain:
|
||||||
|
documentation: ""
|
||||||
|
SQLDatabaseChain:
|
||||||
|
documentation: ""
|
||||||
|
RetrievalQA:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/chains/popular/vector_db_qa"
|
||||||
|
RetrievalQAWithSourcesChain:
|
||||||
|
documentation: ""
|
||||||
|
ConversationalRetrievalChain:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/chains/popular/chat_vector_db"
|
||||||
|
CombineDocsChain:
|
||||||
|
documentation: ""
|
||||||
documentloaders:
|
documentloaders:
|
||||||
- AirbyteJSONLoader
|
AirbyteJSONLoader:
|
||||||
- CoNLLULoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/airbyte_json"
|
||||||
- CSVLoader
|
CoNLLULoader:
|
||||||
- UnstructuredEmailLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/conll-u"
|
||||||
- EverNoteLoader
|
CSVLoader:
|
||||||
- FacebookChatLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/csv"
|
||||||
- GutenbergLoader
|
UnstructuredEmailLoader:
|
||||||
- BSHTMLLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/email"
|
||||||
- UnstructuredHTMLLoader
|
EverNoteLoader:
|
||||||
# - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83)
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/evernote"
|
||||||
- UnstructuredMarkdownLoader
|
FacebookChatLoader:
|
||||||
- PyPDFLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/facebook_chat"
|
||||||
- UnstructuredPowerPointLoader
|
GutenbergLoader:
|
||||||
- SRTLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/gutenberg"
|
||||||
- TelegramChatLoader
|
BSHTMLLoader:
|
||||||
- TextLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/html"
|
||||||
- UnstructuredWordDocumentLoader
|
UnstructuredHTMLLoader:
|
||||||
- WebBaseLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/html"
|
||||||
- AZLyricsLoader
|
UnstructuredMarkdownLoader:
|
||||||
- CollegeConfidentialLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/markdown"
|
||||||
- HNLoader
|
PyPDFDirectoryLoader:
|
||||||
- IFixitLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/pdf"
|
||||||
- IMSDbLoader
|
PyPDFLoader:
|
||||||
- GitbookLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/pdf"
|
||||||
- ReadTheDocsLoader
|
UnstructuredPowerPointLoader:
|
||||||
- SlackDirectoryLoader
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/microsoft_powerpoint"
|
||||||
- NotionDirectoryLoader
|
SRTLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/subtitle"
|
||||||
|
TelegramChatLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/telegram"
|
||||||
|
TextLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/"
|
||||||
|
UnstructuredWordDocumentLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/microsoft_word"
|
||||||
|
WebBaseLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/web_base"
|
||||||
|
AZLyricsLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/azlyrics"
|
||||||
|
CollegeConfidentialLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/college_confidential"
|
||||||
|
HNLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/hacker_news"
|
||||||
|
IFixitLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/ifixit"
|
||||||
|
IMSDbLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/imsdb"
|
||||||
|
GitbookLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/gitbook"
|
||||||
|
ReadTheDocsLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/readthedocs_documentation"
|
||||||
|
SlackDirectoryLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/slack"
|
||||||
|
NotionDirectoryLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/notion"
|
||||||
|
DirectoryLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/how_to/file_directory"
|
||||||
|
GitLoader:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_loaders/integrations/git"
|
||||||
embeddings:
|
embeddings:
|
||||||
- OpenAIEmbeddings
|
OpenAIEmbeddings:
|
||||||
- HuggingFaceEmbeddings
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/openai"
|
||||||
- CohereEmbeddings
|
HuggingFaceEmbeddings:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
|
||||||
|
CohereEmbeddings:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/cohere"
|
||||||
llms:
|
llms:
|
||||||
- OpenAI
|
OpenAI:
|
||||||
# - AzureOpenAI
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/openai"
|
||||||
# - AzureChatOpenAI
|
ChatOpenAI:
|
||||||
- ChatOpenAI
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/openai"
|
||||||
- LlamaCpp
|
LlamaCpp:
|
||||||
- CTransformers
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/llamacpp"
|
||||||
- Cohere
|
CTransformers:
|
||||||
- Anthropic
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/ctransformers"
|
||||||
- ChatAnthropic
|
Cohere:
|
||||||
- HuggingFaceHub
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
|
||||||
|
Anthropic:
|
||||||
|
documentation: ""
|
||||||
|
ChatAnthropic:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic"
|
||||||
|
HuggingFaceHub:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/huggingface_hub"
|
||||||
|
VertexAI:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/google_vertex_ai_palm"
|
||||||
|
###
|
||||||
|
# There's a bug in this component deactivating until we get it sorted: _language_models.py", line 804, in send_message
|
||||||
|
# is_blocked=safety_attributes.get("blocked", False),
|
||||||
|
# AttributeError: 'list' object has no attribute 'get'
|
||||||
|
# ChatVertexAI:
|
||||||
|
# documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/google_vertex_ai_palm"
|
||||||
|
###
|
||||||
memories:
|
memories:
|
||||||
- ConversationBufferMemory
|
# https://github.com/supabase-community/supabase-py/issues/482
|
||||||
- ConversationSummaryMemory
|
# ZepChatMessageHistory:
|
||||||
- ConversationKGMemory
|
# documentation: "https://python.langchain.com/docs/modules/memory/integrations/zep_memory"
|
||||||
|
ConversationEntityMemory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/integrations/entity_memory_with_sqlite"
|
||||||
|
# https://github.com/hwchase17/langchain/issues/6091
|
||||||
|
# SQLiteEntityStore:
|
||||||
|
# documentation: "https://python.langchain.com/docs/modules/memory/integrations/entity_memory_with_sqlite"
|
||||||
|
PostgresChatMessageHistory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/integrations/postgres_chat_message_history"
|
||||||
|
ConversationBufferMemory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/how_to/buffer"
|
||||||
|
ConversationSummaryMemory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/how_to/summary"
|
||||||
|
ConversationKGMemory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/how_to/kg"
|
||||||
|
ConversationBufferWindowMemory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/how_to/buffer_window"
|
||||||
|
VectorStoreRetrieverMemory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/how_to/vectorstore_retriever_memory"
|
||||||
|
MongoDBChatMessageHistory:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/memory/integrations/mongodb_chat_message_history"
|
||||||
prompts:
|
prompts:
|
||||||
- PromptTemplate
|
ChatMessagePromptTemplate:
|
||||||
- FewShotPromptTemplate
|
documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/msg_prompt_templates"
|
||||||
- ZeroShotPrompt
|
HumanMessagePromptTemplate:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/how_to/prompts"
|
||||||
|
SystemMessagePromptTemplate:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/how_to/prompts"
|
||||||
|
ChatPromptTemplate:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/how_to/prompts"
|
||||||
|
PromptTemplate:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/"
|
||||||
textsplitters:
|
textsplitters:
|
||||||
- CharacterTextSplitter
|
CharacterTextSplitter:
|
||||||
- RecursiveCharacterTextSplitter
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
|
||||||
# - LatexTextSplitter
|
RecursiveCharacterTextSplitter:
|
||||||
# - PythonCodeTextSplitter
|
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter"
|
||||||
toolkits:
|
toolkits:
|
||||||
- OpenAPIToolkit
|
OpenAPIToolkit:
|
||||||
- JsonToolkit
|
documentation: ""
|
||||||
- VectorStoreInfo
|
JsonToolkit:
|
||||||
- VectorStoreRouterToolkit
|
documentation: ""
|
||||||
- VectorStoreToolkit
|
VectorStoreInfo:
|
||||||
|
documentation: ""
|
||||||
|
VectorStoreRouterToolkit:
|
||||||
|
documentation: ""
|
||||||
|
VectorStoreToolkit:
|
||||||
|
documentation: ""
|
||||||
tools:
|
tools:
|
||||||
- Search
|
Search:
|
||||||
- PAL-MATH
|
documentation: ""
|
||||||
- Calculator
|
PAL-MATH:
|
||||||
- Serper Search
|
documentation: ""
|
||||||
- Tool
|
Calculator:
|
||||||
- PythonFunctionTool
|
documentation: ""
|
||||||
- PythonFunction
|
Serper Search:
|
||||||
- JsonSpec
|
documentation: ""
|
||||||
- News API
|
Tool:
|
||||||
- TMDB API
|
documentation: ""
|
||||||
- Podcast API
|
PythonFunctionTool:
|
||||||
- QuerySQLDataBaseTool
|
documentation: ""
|
||||||
- InfoSQLDatabaseTool
|
PythonFunction:
|
||||||
- ListSQLDatabaseTool
|
documentation: ""
|
||||||
# - QueryCheckerTool
|
JsonSpec:
|
||||||
- BingSearchRun
|
documentation: ""
|
||||||
- GoogleSearchRun
|
News API:
|
||||||
- GoogleSearchResults
|
documentation: ""
|
||||||
- GoogleSerperRun
|
TMDB API:
|
||||||
- JsonListKeysTool
|
documentation: ""
|
||||||
- JsonGetValueTool
|
Podcast API:
|
||||||
- PythonREPLTool
|
documentation: ""
|
||||||
- PythonAstREPLTool
|
QuerySQLDataBaseTool:
|
||||||
- RequestsGetTool
|
documentation: ""
|
||||||
- RequestsPostTool
|
InfoSQLDatabaseTool:
|
||||||
- RequestsPatchTool
|
documentation: ""
|
||||||
- RequestsPutTool
|
ListSQLDatabaseTool:
|
||||||
- RequestsDeleteTool
|
documentation: ""
|
||||||
- WikipediaQueryRun
|
BingSearchRun:
|
||||||
- WolframAlphaQueryRun
|
documentation: ""
|
||||||
|
GoogleSearchRun:
|
||||||
|
documentation: ""
|
||||||
|
GoogleSearchResults:
|
||||||
|
documentation: ""
|
||||||
|
GoogleSerperRun:
|
||||||
|
documentation: ""
|
||||||
|
JsonListKeysTool:
|
||||||
|
documentation: ""
|
||||||
|
JsonGetValueTool:
|
||||||
|
documentation: ""
|
||||||
|
PythonREPLTool:
|
||||||
|
documentation: ""
|
||||||
|
PythonAstREPLTool:
|
||||||
|
documentation: ""
|
||||||
|
RequestsGetTool:
|
||||||
|
documentation: ""
|
||||||
|
RequestsPostTool:
|
||||||
|
documentation: ""
|
||||||
|
RequestsPatchTool:
|
||||||
|
documentation: ""
|
||||||
|
RequestsPutTool:
|
||||||
|
documentation: ""
|
||||||
|
RequestsDeleteTool:
|
||||||
|
documentation: ""
|
||||||
|
WikipediaQueryRun:
|
||||||
|
documentation: ""
|
||||||
|
WolframAlphaQueryRun:
|
||||||
|
documentation: ""
|
||||||
utilities:
|
utilities:
|
||||||
- BingSearchAPIWrapper
|
BingSearchAPIWrapper:
|
||||||
- GoogleSearchAPIWrapper
|
documentation: ""
|
||||||
- GoogleSerperAPIWrapper
|
GoogleSearchAPIWrapper:
|
||||||
- SearxResults
|
documentation: ""
|
||||||
- SearxSearchWrapper
|
GoogleSerperAPIWrapper:
|
||||||
- SerpAPIWrapper
|
documentation: ""
|
||||||
- WikipediaAPIWrapper
|
SearxResults:
|
||||||
- WolframAlphaAPIWrapper
|
documentation: ""
|
||||||
# - ZapierNLAWrapper
|
SearxSearchWrapper:
|
||||||
- SQLDatabase
|
documentation: ""
|
||||||
|
SerpAPIWrapper:
|
||||||
|
documentation: ""
|
||||||
|
WikipediaAPIWrapper:
|
||||||
|
documentation: ""
|
||||||
|
WolframAlphaAPIWrapper:
|
||||||
|
documentation: ""
|
||||||
|
retrievers:
|
||||||
|
MultiQueryRetriever:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/how_to/MultiQueryRetriever"
|
||||||
|
# https://github.com/supabase-community/supabase-py/issues/482
|
||||||
|
# ZepRetriever:
|
||||||
|
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
|
||||||
vectorstores:
|
vectorstores:
|
||||||
- Chroma
|
Chroma:
|
||||||
- Qdrant
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
|
||||||
- Weaviate
|
Qdrant:
|
||||||
- FAISS
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
|
||||||
|
Weaviate:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/weaviate"
|
||||||
|
FAISS:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
|
||||||
|
Pinecone:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/pinecone"
|
||||||
|
SupabaseVectorStore:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase"
|
||||||
|
MongoDBAtlasVectorSearch:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas"
|
||||||
|
# Requires docarray >=0.32.0 but langchain-serve requires jina 3.15.2 which doesn't support docarray >=0.32.0
|
||||||
|
# DocArrayInMemorySearch:
|
||||||
|
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/docarray_in_memory"
|
||||||
wrappers:
|
wrappers:
|
||||||
- RequestsWrapper
|
RequestsWrapper:
|
||||||
# - ChatPromptTemplate
|
documentation: ""
|
||||||
# - SystemMessagePromptTemplate
|
SQLDatabase:
|
||||||
# - HumanMessagePromptTemplate
|
documentation: ""
|
||||||
|
output_parsers:
|
||||||
|
StructuredOutputParser:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/output_parsers/structured"
|
||||||
|
ResponseSchema:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/model_io/output_parsers/structured"
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ from langflow.template import frontend_node
|
||||||
|
|
||||||
# These should always be instantiated
|
# These should always be instantiated
|
||||||
CUSTOM_NODES = {
|
CUSTOM_NODES = {
|
||||||
"prompts": {
|
# "prompts": {
|
||||||
"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
|
# "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
|
||||||
},
|
# },
|
||||||
"tools": {
|
"tools": {
|
||||||
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
|
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
|
||||||
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
|
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
|
||||||
|
|
@ -21,6 +21,10 @@ CUSTOM_NODES = {
|
||||||
"utilities": {
|
"utilities": {
|
||||||
"SQLDatabase": frontend_node.agents.SQLDatabaseNode(),
|
"SQLDatabase": frontend_node.agents.SQLDatabaseNode(),
|
||||||
},
|
},
|
||||||
|
"memories": {
|
||||||
|
"PostgresChatMessageHistory": frontend_node.memories.PostgresChatMessageHistoryFrontendNode(),
|
||||||
|
"MongoDBChatMessageHistory": frontend_node.memories.MongoDBChatMessageHistoryFrontendNode(),
|
||||||
|
},
|
||||||
"chains": {
|
"chains": {
|
||||||
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),
|
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),
|
||||||
"TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(),
|
"TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(),
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,35 @@
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from sqlmodel import SQLModel, Session, create_engine
|
from sqlmodel import SQLModel, Session, create_engine
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
if settings.database_url and settings.database_url.startswith("sqlite"):
|
||||||
if settings.database_url.startswith("sqlite"):
|
|
||||||
connect_args = {"check_same_thread": False}
|
connect_args = {"check_same_thread": False}
|
||||||
else:
|
else:
|
||||||
connect_args = {}
|
connect_args = {}
|
||||||
|
if not settings.database_url:
|
||||||
|
raise RuntimeError("No database_url provided")
|
||||||
engine = create_engine(settings.database_url, connect_args=connect_args)
|
engine = create_engine(settings.database_url, connect_args=connect_args)
|
||||||
|
|
||||||
|
|
||||||
def create_db_and_tables():
|
def create_db_and_tables():
|
||||||
SQLModel.metadata.create_all(engine)
|
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")
|
||||||
|
|
||||||
|
|
||||||
def get_session():
|
def get_session():
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from langflow.graph.vertex.types import (
|
||||||
ToolkitVertex,
|
ToolkitVertex,
|
||||||
VectorStoreVertex,
|
VectorStoreVertex,
|
||||||
WrapperVertex,
|
WrapperVertex,
|
||||||
|
RetrieverVertex,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -32,4 +33,5 @@ __all__ = [
|
||||||
"ToolkitVertex",
|
"ToolkitVertex",
|
||||||
"VectorStoreVertex",
|
"VectorStoreVertex",
|
||||||
"WrapperVertex",
|
"WrapperVertex",
|
||||||
|
"RetrieverVertex",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,15 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class Edge:
|
class Edge:
|
||||||
def __init__(self, source: "Vertex", target: "Vertex"):
|
def __init__(self, source: "Vertex", target: "Vertex", edge: dict):
|
||||||
self.source: "Vertex" = source
|
self.source: "Vertex" = source
|
||||||
self.target: "Vertex" = target
|
self.target: "Vertex" = target
|
||||||
|
self.source_handle = edge.get("sourceHandle", "")
|
||||||
|
self.target_handle = edge.get("targetHandle", "")
|
||||||
|
# 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD'
|
||||||
|
# target_param is documents
|
||||||
|
self.target_param = self.target_handle.split("|")[1]
|
||||||
|
|
||||||
self.validate_edge()
|
self.validate_edge()
|
||||||
|
|
||||||
def validate_edge(self) -> None:
|
def validate_edge(self) -> None:
|
||||||
|
|
@ -27,12 +33,7 @@ class Edge:
|
||||||
# Get what type of input the target node is expecting
|
# Get what type of input the target node is expecting
|
||||||
|
|
||||||
self.matched_type = next(
|
self.matched_type = next(
|
||||||
(
|
(output for output in self.source_types if output in self.target_reqs),
|
||||||
output
|
|
||||||
for output in self.source_types
|
|
||||||
for target_req in self.target_reqs
|
|
||||||
if output in target_req
|
|
||||||
),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
no_matched_type = self.matched_type is None
|
no_matched_type = self.matched_type is None
|
||||||
|
|
@ -47,6 +48,16 @@ class Edge:
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}"
|
f"Edge(source={self.source.id}, target={self.target.id}, target_param={self.target_param}"
|
||||||
f", matched_type={self.matched_type})"
|
f", matched_type={self.matched_type})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(self.__repr__())
|
||||||
|
|
||||||
|
def __eq__(self, __value: object) -> bool:
|
||||||
|
return (
|
||||||
|
self.__repr__() == __value.__repr__()
|
||||||
|
if isinstance(__value, Edge)
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class Graph:
|
||||||
def generator_build(self) -> Generator:
|
def generator_build(self) -> Generator:
|
||||||
"""Builds each vertex in the graph and yields it."""
|
"""Builds each vertex in the graph and yields it."""
|
||||||
sorted_vertices = self.topological_sort()
|
sorted_vertices = self.topological_sort()
|
||||||
logger.info("Sorted vertices: %s", sorted_vertices)
|
logger.debug("Sorted vertices: %s", sorted_vertices)
|
||||||
yield from sorted_vertices
|
yield from sorted_vertices
|
||||||
|
|
||||||
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
||||||
|
|
@ -179,7 +179,7 @@ class Graph:
|
||||||
raise ValueError(f"Source node {edge['source']} not found")
|
raise ValueError(f"Source node {edge['source']} not found")
|
||||||
if target is None:
|
if target is None:
|
||||||
raise ValueError(f"Target node {edge['target']} not found")
|
raise ValueError(f"Target node {edge['target']} not found")
|
||||||
edges.append(Edge(source, target))
|
edges.append(Edge(source, target, edge))
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
|
def _get_vertex_class(self, node_type: str, node_lc_type: str) -> Type[Vertex]:
|
||||||
|
|
@ -214,3 +214,10 @@ class Graph:
|
||||||
if node_type in node_types:
|
if node_type in node_types:
|
||||||
children.append(node)
|
children.append(node)
|
||||||
return children
|
return children
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
node_ids = [node.id for node in self.nodes]
|
||||||
|
edges_repr = "\n".join(
|
||||||
|
[f"{edge.source.id} --> {edge.target.id}" for edge in self.edges]
|
||||||
|
)
|
||||||
|
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,5 @@
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
from langflow.graph.vertex.types import (
|
from langflow.graph.vertex import types
|
||||||
AgentVertex,
|
|
||||||
ChainVertex,
|
|
||||||
DocumentLoaderVertex,
|
|
||||||
EmbeddingVertex,
|
|
||||||
LLMVertex,
|
|
||||||
MemoryVertex,
|
|
||||||
PromptVertex,
|
|
||||||
TextSplitterVertex,
|
|
||||||
ToolVertex,
|
|
||||||
ToolkitVertex,
|
|
||||||
VectorStoreVertex,
|
|
||||||
WrapperVertex,
|
|
||||||
)
|
|
||||||
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.document_loaders.base import documentloader_creator
|
from langflow.interface.document_loaders.base import documentloader_creator
|
||||||
|
|
@ -25,25 +12,25 @@ from langflow.interface.toolkits.base import toolkits_creator
|
||||||
from langflow.interface.tools.base import tool_creator
|
from langflow.interface.tools.base import tool_creator
|
||||||
from langflow.interface.vector_store.base import vectorstore_creator
|
from langflow.interface.vector_store.base import vectorstore_creator
|
||||||
from langflow.interface.wrappers.base import wrapper_creator
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
|
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
|
|
||||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
|
||||||
|
|
||||||
|
|
||||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
||||||
**{t: PromptVertex for t in prompt_creator.to_list()},
|
**{t: types.PromptVertex for t in prompt_creator.to_list()},
|
||||||
**{t: AgentVertex for t in agent_creator.to_list()},
|
**{t: types.AgentVertex for t in agent_creator.to_list()},
|
||||||
**{t: ChainVertex for t in chain_creator.to_list()},
|
**{t: types.ChainVertex for t in chain_creator.to_list()},
|
||||||
**{t: ToolVertex for t in tool_creator.to_list()},
|
**{t: types.ToolVertex for t in tool_creator.to_list()},
|
||||||
**{t: ToolkitVertex for t in toolkits_creator.to_list()},
|
**{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
|
||||||
**{t: WrapperVertex for t in wrapper_creator.to_list()},
|
**{t: types.WrapperVertex for t in wrapper_creator.to_list()},
|
||||||
**{t: LLMVertex for t in llm_creator.to_list()},
|
**{t: types.LLMVertex for t in llm_creator.to_list()},
|
||||||
**{t: MemoryVertex for t in memory_creator.to_list()},
|
**{t: types.MemoryVertex for t in memory_creator.to_list()},
|
||||||
**{t: EmbeddingVertex for t in embedding_creator.to_list()},
|
**{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
|
||||||
**{t: VectorStoreVertex for t in vectorstore_creator.to_list()},
|
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
|
||||||
**{t: DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||||
**{t: TextSplitterVertex for t in textsplitter_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.RetrieverVertex for t in retriever_creator.to_list()},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
from langflow.cache import utils as cache_utils
|
from langflow.interface.initialize import loading
|
||||||
from langflow.graph.vertex.constants import DIRECT_TYPES
|
|
||||||
from langflow.interface import loading
|
|
||||||
from langflow.interface.listing import ALL_TYPES_DICT
|
from langflow.interface.listing import ALL_TYPES_DICT
|
||||||
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
from langflow.utils.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
|
||||||
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import inspect
|
import inspect
|
||||||
import types
|
import types
|
||||||
import warnings
|
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
@ -26,6 +23,7 @@ class Vertex:
|
||||||
self._parse_data()
|
self._parse_data()
|
||||||
self._built_object = None
|
self._built_object = None
|
||||||
self._built = False
|
self._built = False
|
||||||
|
self.artifacts: Dict[str, Any] = {}
|
||||||
|
|
||||||
def _parse_data(self) -> None:
|
def _parse_data(self) -> None:
|
||||||
self.data = self._data["data"]
|
self.data = self._data["data"]
|
||||||
|
|
@ -46,6 +44,14 @@ class Vertex:
|
||||||
for key, value in template_dicts.items()
|
for key, value in template_dicts.items()
|
||||||
if not value["required"]
|
if not value["required"]
|
||||||
]
|
]
|
||||||
|
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||||
|
self.optional_inputs.extend(
|
||||||
|
[
|
||||||
|
input_type
|
||||||
|
for value in template_dicts.values()
|
||||||
|
for input_type in value.get("input_types", [])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
self.vertex_type = (
|
self.vertex_type = (
|
||||||
|
|
@ -61,6 +67,7 @@ class Vertex:
|
||||||
break
|
break
|
||||||
|
|
||||||
def _build_params(self):
|
def _build_params(self):
|
||||||
|
# sourcery skip: merge-list-append, remove-redundant-if
|
||||||
# Some params are required, some are optional
|
# Some params are required, some are optional
|
||||||
# but most importantly, some params are python base classes
|
# but most importantly, some params are python base classes
|
||||||
# like str and others are LangChain objects like LLMChain, BasePromptTemplate
|
# like str and others are LangChain objects like LLMChain, BasePromptTemplate
|
||||||
|
|
@ -81,8 +88,19 @@ class Vertex:
|
||||||
if isinstance(value, dict)
|
if isinstance(value, dict)
|
||||||
}
|
}
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
|
for edge in self.edges:
|
||||||
|
param_key = edge.target_param
|
||||||
|
if param_key in template_dict:
|
||||||
|
if template_dict[param_key]["list"]:
|
||||||
|
if param_key not in params:
|
||||||
|
params[param_key] = []
|
||||||
|
params[param_key].append(edge.source)
|
||||||
|
elif edge.target.id == self.id:
|
||||||
|
params[param_key] = edge.source
|
||||||
|
|
||||||
for key, value in template_dict.items():
|
for key, value in template_dict.items():
|
||||||
if key == "_type":
|
if key == "_type" or not value.get("show"):
|
||||||
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
|
||||||
|
|
@ -90,132 +108,134 @@ class Vertex:
|
||||||
# Load the type in value.get('suffixes') using
|
# Load the type in value.get('suffixes') using
|
||||||
# what is inside value.get('content')
|
# what is inside value.get('content')
|
||||||
# value.get('value') is the file name
|
# value.get('value') is the file name
|
||||||
file_name = value.get("value")
|
file_path = value.get("file_path")
|
||||||
content = value.get("content")
|
|
||||||
type_to_load = value.get("suffixes")
|
|
||||||
file_path = cache_utils.save_binary_file(
|
|
||||||
content=content, file_name=file_name, accepted_types=type_to_load
|
|
||||||
)
|
|
||||||
|
|
||||||
params[key] = file_path
|
params[key] = file_path
|
||||||
|
elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
|
||||||
|
params[key] = value.get("value")
|
||||||
|
|
||||||
elif value.get("type") not in DIRECT_TYPES:
|
if not value.get("required") and params.get(key) is None:
|
||||||
# Get the edge that connects to this node
|
if value.get("default"):
|
||||||
edges = [
|
params[key] = value.get("default")
|
||||||
edge
|
else:
|
||||||
for edge in self.edges
|
params.pop(key, None)
|
||||||
if edge.target == self and edge.matched_type in value["type"]
|
|
||||||
]
|
|
||||||
|
|
||||||
# Get the output of the node that the edge connects to
|
|
||||||
# if the value['list'] is True, then there will be more
|
|
||||||
# than one time setting to params[key]
|
|
||||||
# so we need to append to a list if it exists
|
|
||||||
# or create a new list if it doesn't
|
|
||||||
|
|
||||||
if value["required"] and not edges:
|
|
||||||
# If a required parameter is not found, raise an error
|
|
||||||
raise ValueError(
|
|
||||||
f"Required input {key} for module {self.vertex_type} not found"
|
|
||||||
)
|
|
||||||
elif value["list"]:
|
|
||||||
# If this is a list parameter, append all sources to a list
|
|
||||||
params[key] = [edge.source for edge in edges]
|
|
||||||
elif edges:
|
|
||||||
# If a single parameter is found, use its source
|
|
||||||
params[key] = edges[0].source
|
|
||||||
|
|
||||||
elif value["required"] or value.get("value"):
|
|
||||||
# If value does not have value this still passes
|
|
||||||
# but then gives a keyError
|
|
||||||
# so we need to check if value has value
|
|
||||||
new_value = value.get("value")
|
|
||||||
if new_value is None:
|
|
||||||
warnings.warn(f"Value for {key} in {self.vertex_type} is None. ")
|
|
||||||
if value.get("type") == "int":
|
|
||||||
with contextlib.suppress(TypeError, ValueError):
|
|
||||||
new_value = int(new_value) # type: ignore
|
|
||||||
params[key] = new_value
|
|
||||||
|
|
||||||
# Add _type to params
|
# Add _type to params
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
def _build(self):
|
def _build(self):
|
||||||
# The params dict is used to build the module
|
"""
|
||||||
# it contains values and keys that point to nodes which
|
Initiate the build process.
|
||||||
# have their own params dict
|
"""
|
||||||
# When build is called, we iterate through the params dict
|
|
||||||
# and if the value is a node, we call build on that node
|
|
||||||
# and use the output of that build as the value for the param
|
|
||||||
# if the value is not a node, then we use the value as the param
|
|
||||||
# and continue
|
|
||||||
# Another aspect is that the node_type is the class that we need to import
|
|
||||||
# and instantiate with these built params
|
|
||||||
logger.debug(f"Building {self.vertex_type}")
|
logger.debug(f"Building {self.vertex_type}")
|
||||||
# Build each node in the params dict
|
self._build_each_node_in_params_dict()
|
||||||
|
self._get_and_instantiate_class()
|
||||||
|
self._validate_built_object()
|
||||||
|
|
||||||
|
self._built = True
|
||||||
|
|
||||||
|
def _build_each_node_in_params_dict(self):
|
||||||
|
"""
|
||||||
|
Iterates over each node in the params dictionary and builds it.
|
||||||
|
"""
|
||||||
for key, value in self.params.copy().items():
|
for key, value in self.params.copy().items():
|
||||||
# Check if Node or list of Nodes and not self
|
if self._is_node(value):
|
||||||
# to avoid recursion
|
|
||||||
if isinstance(value, Vertex):
|
|
||||||
if value == self:
|
if value == self:
|
||||||
del self.params[key]
|
del self.params[key]
|
||||||
continue
|
continue
|
||||||
result = value.build()
|
self._build_node_and_update_params(key, value)
|
||||||
# If the key is "func", then we need to use the run method
|
elif isinstance(value, list) and self._is_list_of_nodes(value):
|
||||||
if key == "func":
|
self._build_list_of_nodes_and_update_params(key, value)
|
||||||
if not isinstance(result, types.FunctionType):
|
|
||||||
# func can be
|
|
||||||
# PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n')
|
|
||||||
# so we need to check if there is an attribute called run
|
|
||||||
if hasattr(result, "run"):
|
|
||||||
result = result.run # type: ignore
|
|
||||||
elif hasattr(result, "get_function"):
|
|
||||||
result = result.get_function() # type: ignore
|
|
||||||
elif inspect.iscoroutinefunction(result):
|
|
||||||
self.params["coroutine"] = result
|
|
||||||
else:
|
|
||||||
# turn result which is a function into a coroutine
|
|
||||||
# so that it can be awaited
|
|
||||||
self.params["coroutine"] = sync_to_async(result)
|
|
||||||
if isinstance(result, list):
|
|
||||||
# If the result is a list, then we need to extend the list
|
|
||||||
# with the result but first check if the key exists
|
|
||||||
# if it doesn't, then we need to create a new list
|
|
||||||
if isinstance(self.params[key], list):
|
|
||||||
self.params[key].extend(result)
|
|
||||||
|
|
||||||
self.params[key] = result
|
def _is_node(self, value):
|
||||||
elif isinstance(value, list) and all(
|
"""
|
||||||
isinstance(node, Vertex) for node in value
|
Checks if the provided value is an instance of Vertex.
|
||||||
):
|
"""
|
||||||
self.params[key] = []
|
return isinstance(value, Vertex)
|
||||||
for node in value:
|
|
||||||
built = node.build()
|
|
||||||
if isinstance(built, list):
|
|
||||||
self.params[key].extend(built)
|
|
||||||
else:
|
|
||||||
self.params[key].append(built)
|
|
||||||
|
|
||||||
# Get the class from LANGCHAIN_TYPES_DICT
|
def _is_list_of_nodes(self, value):
|
||||||
# and instantiate it with the params
|
"""
|
||||||
# and return the instance
|
Checks if the provided value is a list of Vertex instances.
|
||||||
|
"""
|
||||||
|
return all(self._is_node(node) for node in value)
|
||||||
|
|
||||||
|
def _build_node_and_update_params(self, key, node):
|
||||||
|
"""
|
||||||
|
Builds a given node and updates the params dictionary accordingly.
|
||||||
|
"""
|
||||||
|
result = node.build()
|
||||||
|
self._handle_func(key, result)
|
||||||
|
if isinstance(result, list):
|
||||||
|
self._extend_params_list_with_result(key, result)
|
||||||
|
self.params[key] = result
|
||||||
|
|
||||||
|
def _build_list_of_nodes_and_update_params(self, key, nodes):
|
||||||
|
"""
|
||||||
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
|
"""
|
||||||
|
self.params[key] = []
|
||||||
|
for node in nodes:
|
||||||
|
built = node.build()
|
||||||
|
if isinstance(built, list):
|
||||||
|
self.params[key].extend(built)
|
||||||
|
else:
|
||||||
|
self.params[key].append(built)
|
||||||
|
|
||||||
|
def _handle_func(self, key, result):
|
||||||
|
"""
|
||||||
|
Handles 'func' key by checking if the result is a function and setting it as coroutine.
|
||||||
|
"""
|
||||||
|
if key == "func":
|
||||||
|
if not isinstance(result, types.FunctionType):
|
||||||
|
if hasattr(result, "run"):
|
||||||
|
result = result.run # type: ignore
|
||||||
|
elif hasattr(result, "get_function"):
|
||||||
|
result = result.get_function() # type: ignore
|
||||||
|
elif inspect.iscoroutinefunction(result):
|
||||||
|
self.params["coroutine"] = result
|
||||||
|
else:
|
||||||
|
self.params["coroutine"] = sync_to_async(result)
|
||||||
|
|
||||||
|
def _extend_params_list_with_result(self, key, result):
|
||||||
|
"""
|
||||||
|
Extends a list in the params dictionary with the given result if it exists.
|
||||||
|
"""
|
||||||
|
if isinstance(self.params[key], list):
|
||||||
|
self.params[key].extend(result)
|
||||||
|
|
||||||
|
def _get_and_instantiate_class(self):
|
||||||
|
"""
|
||||||
|
Gets the class from a dictionary and instantiates it with the params.
|
||||||
|
"""
|
||||||
|
if self.base_type is None:
|
||||||
|
raise ValueError(f"Base type for node {self.vertex_type} not found")
|
||||||
try:
|
try:
|
||||||
self._built_object = loading.instantiate_class(
|
result = loading.instantiate_class(
|
||||||
node_type=self.vertex_type,
|
node_type=self.vertex_type,
|
||||||
base_type=self.base_type,
|
base_type=self.base_type,
|
||||||
params=self.params,
|
params=self.params,
|
||||||
)
|
)
|
||||||
|
self._update_built_object_and_artifacts(result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Error building node {self.vertex_type}: {str(exc)}"
|
f"Error building node {self.vertex_type}: {str(exc)}"
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
|
def _update_built_object_and_artifacts(self, result):
|
||||||
|
"""
|
||||||
|
Updates the built object and its artifacts.
|
||||||
|
"""
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
self._built_object, self.artifacts = result
|
||||||
|
else:
|
||||||
|
self._built_object = result
|
||||||
|
|
||||||
|
def _validate_built_object(self):
|
||||||
|
"""
|
||||||
|
Checks if the built object is None and raises a ValueError if so.
|
||||||
|
"""
|
||||||
if self._built_object is None:
|
if self._built_object is None:
|
||||||
raise ValueError(f"Node type {self.vertex_type} not found")
|
raise ValueError(f"Node type {self.vertex_type} not found")
|
||||||
|
|
||||||
self._built = True
|
|
||||||
|
|
||||||
def build(self, force: bool = False) -> Any:
|
def build(self, force: bool = False) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
self._build()
|
self._build()
|
||||||
|
|
@ -223,10 +243,11 @@ class Vertex:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
def add_edge(self, edge: "Edge") -> None:
|
def add_edge(self, edge: "Edge") -> None:
|
||||||
self.edges.append(edge)
|
if edge not in self.edges:
|
||||||
|
self.edges.append(edge)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Node(id={self.id}, data={self.data})"
|
return f"Vertex(id={self.id}, data={self.data})"
|
||||||
|
|
||||||
def __eq__(self, __o: object) -> bool:
|
def __eq__(self, __o: object) -> bool:
|
||||||
return self.id == __o.id if isinstance(__o, Vertex) else False
|
return self.id == __o.id if isinstance(__o, Vertex) else False
|
||||||
|
|
@ -235,4 +256,5 @@ class Vertex:
|
||||||
return id(self)
|
return id(self)
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
return repr(self._built_object)
|
# Add a message with an emoji, stars for sucess,
|
||||||
|
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵💫"
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import ast
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
|
|
@ -79,7 +80,7 @@ class WrapperVertex(Vertex):
|
||||||
def build(self, force: bool = False) -> Any:
|
def build(self, force: bool = False) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
if "headers" in self.params:
|
if "headers" in self.params:
|
||||||
self.params["headers"] = eval(self.params["headers"])
|
self.params["headers"] = ast.literal_eval(self.params["headers"])
|
||||||
self._build()
|
self._build()
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
|
|
@ -91,8 +92,13 @@ class DocumentLoaderVertex(Vertex):
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
# This built_object is a list of documents. Maybe we should
|
# This built_object is a list of documents. Maybe we should
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if self._built_object:
|
if self._built_object:
|
||||||
|
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
||||||
|
self._built_object
|
||||||
|
)
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
|
\nAvg. Document Length (characters): {avg_length}
|
||||||
Documents: {self._built_object[:3]}..."""
|
Documents: {self._built_object[:3]}..."""
|
||||||
return f"{self.vertex_type}()"
|
return f"{self.vertex_type}()"
|
||||||
|
|
||||||
|
|
@ -106,15 +112,17 @@ class VectorStoreVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="vectorstores")
|
super().__init__(data, base_type="vectorstores")
|
||||||
|
|
||||||
def _built_object_repr(self):
|
|
||||||
return "Vector stores can take time to build. It will build on the first query."
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryVertex(Vertex):
|
class MemoryVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="memory")
|
super().__init__(data, base_type="memory")
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieverVertex(Vertex):
|
||||||
|
def __init__(self, data: Dict):
|
||||||
|
super().__init__(data, base_type="retrievers")
|
||||||
|
|
||||||
|
|
||||||
class TextSplitterVertex(Vertex):
|
class TextSplitterVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="textsplitters")
|
super().__init__(data, base_type="textsplitters")
|
||||||
|
|
@ -122,8 +130,13 @@ class TextSplitterVertex(Vertex):
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
# This built_object is a list of documents. Maybe we should
|
# This built_object is a list of documents. Maybe we should
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if self._built_object:
|
if self._built_object:
|
||||||
|
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
||||||
|
self._built_object
|
||||||
|
)
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
|
\nAvg. Document Length (characters): {avg_length}
|
||||||
\nDocuments: {self._built_object[:3]}..."""
|
\nDocuments: {self._built_object[:3]}..."""
|
||||||
return f"{self.vertex_type}()"
|
return f"{self.vertex_type}()"
|
||||||
|
|
||||||
|
|
@ -183,11 +196,46 @@ class PromptVertex(Vertex):
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
prompt_params = ["template"]
|
prompt_params = ["template"]
|
||||||
for param in prompt_params:
|
|
||||||
prompt_text = self.params[param]
|
if "prompt" not in self.params and "messages" not in self.params:
|
||||||
variables = extract_input_variables_from_prompt(prompt_text)
|
for param in prompt_params:
|
||||||
self.params["input_variables"].extend(variables)
|
prompt_text = self.params[param]
|
||||||
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
variables = extract_input_variables_from_prompt(prompt_text)
|
||||||
|
self.params["input_variables"].extend(variables)
|
||||||
|
self.params["input_variables"] = list(
|
||||||
|
set(self.params["input_variables"])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.params.pop("input_variables", None)
|
||||||
|
|
||||||
self._build()
|
self._build()
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
|
def _built_object_repr(self):
|
||||||
|
if (
|
||||||
|
not self.artifacts
|
||||||
|
or self._built_object is None
|
||||||
|
or not hasattr(self._built_object, "format")
|
||||||
|
):
|
||||||
|
return super()._built_object_repr()
|
||||||
|
# We'll build the prompt with the artifacts
|
||||||
|
# to show the user what the prompt looks like
|
||||||
|
# with the variables filled in
|
||||||
|
artifacts = self.artifacts.copy()
|
||||||
|
# Remove the handle_keys from the artifacts
|
||||||
|
# so the prompt format doesn't break
|
||||||
|
artifacts.pop("handle_keys", None)
|
||||||
|
try:
|
||||||
|
template = self._built_object.format(**artifacts)
|
||||||
|
return (
|
||||||
|
template
|
||||||
|
if isinstance(template, str)
|
||||||
|
else f"{self.vertex_type}({template})"
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
return str(self._built_object)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputParserVertex(Vertex):
|
||||||
|
def __init__(self, data: Dict):
|
||||||
|
super().__init__(data, base_type="output_parsers")
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,20 @@ 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.settings import settings
|
||||||
|
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
|
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||||
|
|
||||||
|
|
||||||
class AgentCreator(LangChainTypeCreator):
|
class AgentCreator(LangChainTypeCreator):
|
||||||
type_name: str = "agents"
|
type_name: str = "agents"
|
||||||
|
|
||||||
|
from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> type[AgentFrontendNode]:
|
||||||
|
return AgentFrontendNode
|
||||||
|
|
||||||
@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:
|
||||||
|
|
@ -27,6 +34,13 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
try:
|
try:
|
||||||
if name in get_custom_nodes(self.type_name).keys():
|
if name in get_custom_nodes(self.type_name).keys():
|
||||||
return get_custom_nodes(self.type_name)[name]
|
return get_custom_nodes(self.type_name)[name]
|
||||||
|
elif name in self.from_method_nodes:
|
||||||
|
return build_template_from_method(
|
||||||
|
name,
|
||||||
|
type_to_cls_dict=self.type_to_loader_dict,
|
||||||
|
add_function=True,
|
||||||
|
method_name=self.from_method_nodes[name],
|
||||||
|
)
|
||||||
return build_template_from_class(
|
return build_template_from_class(
|
||||||
name, self.type_to_loader_dict, add_function=True
|
name, self.type_to_loader_dict, add_function=True
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from langchain.agents import (
|
||||||
Tool,
|
Tool,
|
||||||
ZeroShotAgent,
|
ZeroShotAgent,
|
||||||
initialize_agent,
|
initialize_agent,
|
||||||
|
AgentType,
|
||||||
)
|
)
|
||||||
from langchain.agents.agent_toolkits import (
|
from langchain.agents.agent_toolkits import (
|
||||||
SQLDatabaseToolkit,
|
SQLDatabaseToolkit,
|
||||||
|
|
@ -156,7 +157,7 @@ class VectorStoreAgent(CustomAgentExecutor):
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
||||||
)
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent, tools=tools, verbose=True
|
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
|
|
@ -192,7 +193,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
from langchain.tools.sql_database.tool import (
|
from langchain.tools.sql_database.tool import (
|
||||||
InfoSQLDatabaseTool,
|
InfoSQLDatabaseTool,
|
||||||
ListSQLDatabaseTool,
|
ListSQLDatabaseTool,
|
||||||
QueryCheckerTool,
|
QuerySQLCheckerTool,
|
||||||
QuerySQLDataBaseTool,
|
QuerySQLDataBaseTool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -207,7 +208,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
QuerySQLDataBaseTool(db=db), # type: ignore
|
QuerySQLDataBaseTool(db=db), # type: ignore
|
||||||
InfoSQLDatabaseTool(db=db), # type: ignore
|
InfoSQLDatabaseTool(db=db), # type: ignore
|
||||||
ListSQLDatabaseTool(db=db), # type: ignore
|
ListSQLDatabaseTool(db=db), # type: ignore
|
||||||
QueryCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
QuerySQLCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
||||||
]
|
]
|
||||||
|
|
||||||
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
||||||
|
|
@ -231,6 +232,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
verbose=True,
|
verbose=True,
|
||||||
max_iterations=15,
|
max_iterations=15,
|
||||||
early_stopping_method="force",
|
early_stopping_method="force",
|
||||||
|
handle_parsing_errors=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
|
|
@ -275,7 +277,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
||||||
)
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent, tools=tools, verbose=True
|
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
|
|
@ -297,6 +299,9 @@ class InitializeAgent(CustomAgentExecutor):
|
||||||
agent: str,
|
agent: str,
|
||||||
memory: Optional[BaseChatMemory] = None,
|
memory: Optional[BaseChatMemory] = None,
|
||||||
):
|
):
|
||||||
|
# Find which value in the AgentType enum corresponds to the string
|
||||||
|
# passed in as agent
|
||||||
|
agent = AgentType(agent)
|
||||||
return initialize_agent(
|
return initialize_agent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
llm=llm,
|
llm=llm,
|
||||||
|
|
@ -304,6 +309,7 @@ class InitializeAgent(CustomAgentExecutor):
|
||||||
agent=agent, # type: ignore
|
agent=agent, # type: ignore
|
||||||
memory=memory,
|
memory=memory,
|
||||||
return_intermediate_steps=True,
|
return_intermediate_steps=True,
|
||||||
|
handle_parsing_errors=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ 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
|
||||||
|
|
||||||
|
|
@ -15,12 +16,29 @@ from langflow.utils.logger import logger
|
||||||
class LangChainTypeCreator(BaseModel, ABC):
|
class LangChainTypeCreator(BaseModel, ABC):
|
||||||
type_name: str
|
type_name: str
|
||||||
type_dict: Optional[Dict] = None
|
type_dict: Optional[Dict] = None
|
||||||
|
name_docs_dict: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frontend_node_class(self) -> Type[FrontendNode]:
|
def frontend_node_class(self) -> Type[FrontendNode]:
|
||||||
"""The class type of the FrontendNode created in frontend_node."""
|
"""The class type of the FrontendNode created in frontend_node."""
|
||||||
return FrontendNode
|
return FrontendNode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def docs_map(self) -> Dict[str, str]:
|
||||||
|
"""A dict with the name of the component as key and the documentation link as value."""
|
||||||
|
if self.name_docs_dict is None:
|
||||||
|
try:
|
||||||
|
type_settings = getattr(settings, self.type_name)
|
||||||
|
self.name_docs_dict = {
|
||||||
|
name: value_dict["documentation"]
|
||||||
|
for name, value_dict in type_settings.items()
|
||||||
|
}
|
||||||
|
except AttributeError as exc:
|
||||||
|
logger.error(exc)
|
||||||
|
|
||||||
|
self.name_docs_dict = {}
|
||||||
|
return self.name_docs_dict
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
|
@ -68,7 +86,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
value=value.get("value", None),
|
value=value.get("value", None),
|
||||||
suffixes=value.get("suffixes", []),
|
suffixes=value.get("suffixes", []),
|
||||||
file_types=value.get("fileTypes", []),
|
file_types=value.get("fileTypes", []),
|
||||||
content=value.get("content", None),
|
file_path=value.get("file_path", None),
|
||||||
)
|
)
|
||||||
for key, value in signature["template"].items()
|
for key, value in signature["template"].items()
|
||||||
if key != "_type"
|
if key != "_type"
|
||||||
|
|
@ -83,7 +101,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
|
|
||||||
signature.add_extra_fields()
|
signature.add_extra_fields()
|
||||||
signature.add_extra_base_classes()
|
signature.add_extra_base_classes()
|
||||||
|
signature.set_documentation(self.docs_map.get(name, ""))
|
||||||
return signature
|
return signature
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
from_method_nodes = {
|
from_method_nodes = {
|
||||||
"ConversationalRetrievalChain": "from_llm",
|
"ConversationalRetrievalChain": "from_llm",
|
||||||
"LLMCheckerChain": "from_llm",
|
"LLMCheckerChain": "from_llm",
|
||||||
|
"SQLDatabaseChain": "from_llm",
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,12 @@ from langchain import (
|
||||||
text_splitter,
|
text_splitter,
|
||||||
)
|
)
|
||||||
from langchain.agents import agent_toolkits
|
from langchain.agents import agent_toolkits
|
||||||
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
|
from langchain.chat_models import (
|
||||||
from langchain.chat_models import ChatAnthropic
|
AzureChatOpenAI,
|
||||||
|
ChatOpenAI,
|
||||||
|
ChatVertexAI,
|
||||||
|
ChatAnthropic,
|
||||||
|
)
|
||||||
|
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||||
|
|
@ -22,6 +26,7 @@ llm_type_to_cls_dict = llms.type_to_cls_dict
|
||||||
llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
|
llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
|
||||||
llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore
|
llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore
|
||||||
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
|
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
|
||||||
|
llm_type_to_cls_dict["vertexai-chat"] = ChatVertexAI # type: ignore
|
||||||
|
|
||||||
|
|
||||||
# Toolkits
|
# Toolkits
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from langchain.chains.base import Chain
|
||||||
from langchain.chat_models.base import BaseChatModel
|
from langchain.chat_models.base import BaseChatModel
|
||||||
from langchain.tools import BaseTool
|
from langchain.tools import BaseTool
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
|
|
||||||
|
|
||||||
def import_module(module_path: str) -> Any:
|
def import_module(module_path: str) -> Any:
|
||||||
|
|
@ -44,6 +45,8 @@ def import_by_type(_type: str, name: str) -> Any:
|
||||||
"documentloaders": import_documentloader,
|
"documentloaders": import_documentloader,
|
||||||
"textsplitters": import_textsplitter,
|
"textsplitters": import_textsplitter,
|
||||||
"utilities": import_utility,
|
"utilities": import_utility,
|
||||||
|
"output_parsers": import_output_parser,
|
||||||
|
"retrievers": import_retriever,
|
||||||
}
|
}
|
||||||
if _type == "llms":
|
if _type == "llms":
|
||||||
key = "chat" if "chat" in name.lower() else "llm"
|
key = "chat" if "chat" in name.lower() else "llm"
|
||||||
|
|
@ -54,11 +57,21 @@ def import_by_type(_type: str, name: str) -> Any:
|
||||||
return loaded_func(name)
|
return loaded_func(name)
|
||||||
|
|
||||||
|
|
||||||
|
def import_output_parser(output_parser: str) -> Any:
|
||||||
|
"""Import output parser from output parser name"""
|
||||||
|
return import_module(f"from langchain.output_parsers import {output_parser}")
|
||||||
|
|
||||||
|
|
||||||
def import_chat_llm(llm: str) -> BaseChatModel:
|
def import_chat_llm(llm: str) -> BaseChatModel:
|
||||||
"""Import chat llm from llm name"""
|
"""Import chat llm from llm name"""
|
||||||
return import_class(f"langchain.chat_models.{llm}")
|
return import_class(f"langchain.chat_models.{llm}")
|
||||||
|
|
||||||
|
|
||||||
|
def import_retriever(retriever: str) -> Any:
|
||||||
|
"""Import retriever from retriever name"""
|
||||||
|
return import_module(f"from langchain.retrievers import {retriever}")
|
||||||
|
|
||||||
|
|
||||||
def import_memory(memory: str) -> Any:
|
def import_memory(memory: str) -> Any:
|
||||||
"""Import memory from memory name"""
|
"""Import memory from memory name"""
|
||||||
return import_module(f"from langchain.memory import {memory}")
|
return import_module(f"from langchain.memory import {memory}")
|
||||||
|
|
@ -84,7 +97,11 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]:
|
||||||
|
|
||||||
def import_wrapper(wrapper: str) -> Any:
|
def import_wrapper(wrapper: str) -> Any:
|
||||||
"""Import wrapper from wrapper name"""
|
"""Import wrapper from wrapper name"""
|
||||||
return import_module(f"from langchain.requests import {wrapper}")
|
if (
|
||||||
|
isinstance(wrapper_creator.type_dict, dict)
|
||||||
|
and wrapper in wrapper_creator.type_dict
|
||||||
|
):
|
||||||
|
return wrapper_creator.type_dict.get(wrapper)
|
||||||
|
|
||||||
|
|
||||||
def import_toolkit(toolkit: str) -> Any:
|
def import_toolkit(toolkit: str) -> Any:
|
||||||
|
|
|
||||||
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
9
src/backend/langflow/interface/initialize/llm.py
Normal file
9
src/backend/langflow/interface/initialize/llm.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
def initialize_vertexai(class_object, params):
|
||||||
|
if credentials_path := params.get("credentials"):
|
||||||
|
from google.oauth2 import service_account # type: ignore
|
||||||
|
|
||||||
|
credentials_object = service_account.Credentials.from_service_account_file(
|
||||||
|
filename=credentials_path
|
||||||
|
)
|
||||||
|
params["credentials"] = credentials_object
|
||||||
|
return class_object(**params)
|
||||||
516
src/backend/langflow/interface/initialize/loading.py
Normal file
516
src/backend/langflow/interface/initialize/loading.py
Normal file
|
|
@ -0,0 +1,516 @@
|
||||||
|
import contextlib
|
||||||
|
import json
|
||||||
|
from typing import Any, Callable, Dict, List, Sequence, Type
|
||||||
|
|
||||||
|
from langchain.agents import ZeroShotAgent
|
||||||
|
from langchain.agents import agent as agent_module
|
||||||
|
from langchain.agents.agent import AgentExecutor
|
||||||
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
|
from langchain.agents.tools import BaseTool
|
||||||
|
from langflow.interface.initialize.llm import initialize_vertexai
|
||||||
|
|
||||||
|
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||||
|
|
||||||
|
from langchain.schema import Document, BaseOutputParser
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
from langflow.interface.custom_lists import CUSTOM_NODES
|
||||||
|
from langflow.interface.importing.utils import get_function, import_by_type
|
||||||
|
from langflow.interface.agents.base import agent_creator
|
||||||
|
from langflow.interface.toolkits.base import toolkits_creator
|
||||||
|
from langflow.interface.chains.base import chain_creator
|
||||||
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
|
from langflow.interface.utils import load_file_into_dict
|
||||||
|
from langflow.utils import validate
|
||||||
|
from langchain.chains.base import Chain
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
|
from langchain.document_loaders.base import BaseLoader
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
|
"""Instantiate class from module type and key, and params"""
|
||||||
|
params = convert_params_to_sets(params)
|
||||||
|
params = convert_kwargs(params)
|
||||||
|
if node_type in CUSTOM_NODES:
|
||||||
|
if custom_node := CUSTOM_NODES.get(node_type):
|
||||||
|
if hasattr(custom_node, "initialize"):
|
||||||
|
return custom_node.initialize(**params)
|
||||||
|
return custom_node(**params)
|
||||||
|
|
||||||
|
class_object = import_by_type(_type=base_type, name=node_type)
|
||||||
|
return instantiate_based_on_type(class_object, base_type, node_type, params)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_params_to_sets(params):
|
||||||
|
"""Convert certain params to sets"""
|
||||||
|
if "allowed_special" in params:
|
||||||
|
params["allowed_special"] = set(params["allowed_special"])
|
||||||
|
if "disallowed_special" in params:
|
||||||
|
params["disallowed_special"] = set(params["disallowed_special"])
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def convert_kwargs(params):
|
||||||
|
# if *kwargs are passed as a string, convert to dict
|
||||||
|
# first find any key that has kwargs or config in it
|
||||||
|
kwargs_keys = [key for key in params.keys() if "kwargs" in key or "config" in key]
|
||||||
|
for key in kwargs_keys:
|
||||||
|
if isinstance(params[key], str):
|
||||||
|
params[key] = json.loads(params[key])
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_based_on_type(class_object, base_type, node_type, params):
|
||||||
|
if base_type == "agents":
|
||||||
|
return instantiate_agent(node_type, class_object, params)
|
||||||
|
elif base_type == "prompts":
|
||||||
|
return instantiate_prompt(node_type, class_object, params)
|
||||||
|
elif base_type == "tools":
|
||||||
|
tool = instantiate_tool(node_type, class_object, params)
|
||||||
|
if hasattr(tool, "name") and isinstance(tool, BaseTool):
|
||||||
|
# tool name shouldn't contain spaces
|
||||||
|
tool.name = tool.name.replace(" ", "_")
|
||||||
|
return tool
|
||||||
|
elif base_type == "toolkits":
|
||||||
|
return instantiate_toolkit(node_type, class_object, params)
|
||||||
|
elif base_type == "embeddings":
|
||||||
|
return instantiate_embedding(class_object, params)
|
||||||
|
elif base_type == "vectorstores":
|
||||||
|
return instantiate_vectorstore(class_object, params)
|
||||||
|
elif base_type == "documentloaders":
|
||||||
|
return instantiate_documentloader(class_object, params)
|
||||||
|
elif base_type == "textsplitters":
|
||||||
|
return instantiate_textsplitter(class_object, params)
|
||||||
|
elif base_type == "utilities":
|
||||||
|
return instantiate_utility(node_type, class_object, params)
|
||||||
|
elif base_type == "chains":
|
||||||
|
return instantiate_chains(node_type, class_object, params)
|
||||||
|
elif base_type == "output_parsers":
|
||||||
|
return instantiate_output_parser(node_type, class_object, params)
|
||||||
|
elif base_type == "llms":
|
||||||
|
return instantiate_llm(node_type, class_object, params)
|
||||||
|
elif base_type == "retrievers":
|
||||||
|
return instantiate_retriever(node_type, class_object, params)
|
||||||
|
elif base_type == "memory":
|
||||||
|
return instantiate_memory(node_type, class_object, params)
|
||||||
|
elif base_type == "wrappers":
|
||||||
|
return instantiate_wrapper(node_type, class_object, params)
|
||||||
|
else:
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_wrapper(node_type, class_object, params):
|
||||||
|
if node_type in wrapper_creator.from_method_nodes:
|
||||||
|
method = wrapper_creator.from_method_nodes[node_type]
|
||||||
|
if class_method := getattr(class_object, method, None):
|
||||||
|
return class_method(**params)
|
||||||
|
raise ValueError(f"Method {method} not found in {class_object}")
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_output_parser(node_type, class_object, params):
|
||||||
|
if node_type in output_parser_creator.from_method_nodes:
|
||||||
|
method = output_parser_creator.from_method_nodes[node_type]
|
||||||
|
if class_method := getattr(class_object, method, None):
|
||||||
|
return class_method(**params)
|
||||||
|
raise ValueError(f"Method {method} not found in {class_object}")
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_llm(node_type, class_object, params: Dict):
|
||||||
|
# This is a workaround so JinaChat works until streaming is implemented
|
||||||
|
# if "openai_api_base" in params and "jina" in params["openai_api_base"]:
|
||||||
|
# False if condition is True
|
||||||
|
if node_type == "VertexAI":
|
||||||
|
return initialize_vertexai(class_object=class_object, params=params)
|
||||||
|
# max_tokens sometimes is a string and should be an int
|
||||||
|
if "max_tokens" in params:
|
||||||
|
if isinstance(params["max_tokens"], str) and params["max_tokens"].isdigit():
|
||||||
|
params["max_tokens"] = int(params["max_tokens"])
|
||||||
|
elif not isinstance(params.get("max_tokens"), int):
|
||||||
|
params.pop("max_tokens", None)
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_memory(node_type, class_object, params):
|
||||||
|
# process input_key and output_key to remove them if
|
||||||
|
# they are empty strings
|
||||||
|
if node_type == "ConversationEntityMemory":
|
||||||
|
params.pop("memory_key", None)
|
||||||
|
|
||||||
|
for key in ["input_key", "output_key"]:
|
||||||
|
if key in params and (params[key] == "" or not params[key]):
|
||||||
|
params.pop(key)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
||||||
|
params["retriever"] = params["retriever"].as_retriever()
|
||||||
|
return class_object(**params)
|
||||||
|
# I want to catch a specific attribute error that happens
|
||||||
|
# when the object does not have a cursor attribute
|
||||||
|
except Exception as exc:
|
||||||
|
if "object has no attribute 'cursor'" in str(
|
||||||
|
exc
|
||||||
|
) or 'object has no field "conn"' in str(exc):
|
||||||
|
raise AttributeError(
|
||||||
|
(
|
||||||
|
"Failed to build connection to database."
|
||||||
|
f" Please check your connection string and try again. Error: {exc}"
|
||||||
|
)
|
||||||
|
) from exc
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_retriever(node_type, class_object, params):
|
||||||
|
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
||||||
|
params["retriever"] = params["retriever"].as_retriever()
|
||||||
|
if node_type in retriever_creator.from_method_nodes:
|
||||||
|
method = retriever_creator.from_method_nodes[node_type]
|
||||||
|
if class_method := getattr(class_object, method, None):
|
||||||
|
return class_method(**params)
|
||||||
|
raise ValueError(f"Method {method} not found in {class_object}")
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
|
||||||
|
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
||||||
|
params["retriever"] = params["retriever"].as_retriever()
|
||||||
|
if node_type in chain_creator.from_method_nodes:
|
||||||
|
method = chain_creator.from_method_nodes[node_type]
|
||||||
|
if class_method := getattr(class_object, method, None):
|
||||||
|
return class_method(**params)
|
||||||
|
raise ValueError(f"Method {method} not found in {class_object}")
|
||||||
|
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
|
||||||
|
if node_type in agent_creator.from_method_nodes:
|
||||||
|
method = agent_creator.from_method_nodes[node_type]
|
||||||
|
if class_method := getattr(class_object, method, None):
|
||||||
|
agent = class_method(**params)
|
||||||
|
tools = params.get("tools", [])
|
||||||
|
return AgentExecutor.from_agent_and_tools(
|
||||||
|
agent=agent, tools=tools, handle_parsing_errors=True
|
||||||
|
)
|
||||||
|
return load_agent_executor(class_object, params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_prompt(node_type, class_object, params: Dict):
|
||||||
|
if node_type == "ZeroShotPrompt":
|
||||||
|
if "tools" not in params:
|
||||||
|
params["tools"] = []
|
||||||
|
return ZeroShotAgent.create_prompt(**params)
|
||||||
|
elif "MessagePromptTemplate" in node_type:
|
||||||
|
# Then we only need the template
|
||||||
|
from_template_params = {
|
||||||
|
"template": params.pop("prompt", params.pop("template", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
if not from_template_params.get("template"):
|
||||||
|
raise ValueError("Prompt template is required")
|
||||||
|
prompt = class_object.from_template(**from_template_params)
|
||||||
|
|
||||||
|
elif node_type == "ChatPromptTemplate":
|
||||||
|
prompt = class_object.from_messages(**params)
|
||||||
|
else:
|
||||||
|
prompt = class_object(**params)
|
||||||
|
|
||||||
|
format_kwargs: Dict[str, Any] = {}
|
||||||
|
for input_variable in prompt.input_variables:
|
||||||
|
if input_variable in params:
|
||||||
|
variable = params[input_variable]
|
||||||
|
if isinstance(variable, str):
|
||||||
|
format_kwargs[input_variable] = variable
|
||||||
|
elif isinstance(variable, BaseOutputParser) and hasattr(
|
||||||
|
variable, "get_format_instructions"
|
||||||
|
):
|
||||||
|
format_kwargs[input_variable] = variable.get_format_instructions()
|
||||||
|
elif isinstance(variable, List) and all(
|
||||||
|
isinstance(item, Document) for item in variable
|
||||||
|
):
|
||||||
|
# Format document to contain page_content and metadata
|
||||||
|
# as one string separated by a newline
|
||||||
|
if len(variable) > 1:
|
||||||
|
content = "\n".join(
|
||||||
|
[item.page_content for item in variable if item.page_content]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
content = variable[0].page_content
|
||||||
|
# content could be a json list of strings
|
||||||
|
with contextlib.suppress(json.JSONDecodeError):
|
||||||
|
content = json.loads(content)
|
||||||
|
if isinstance(content, list):
|
||||||
|
content = ",".join([str(item) for item in content])
|
||||||
|
format_kwargs[input_variable] = content
|
||||||
|
# handle_keys will be a list but it does not exist yet
|
||||||
|
# so we need to create it
|
||||||
|
|
||||||
|
if (
|
||||||
|
isinstance(variable, List)
|
||||||
|
and all(isinstance(item, Document) for item in variable)
|
||||||
|
) or (
|
||||||
|
isinstance(variable, BaseOutputParser)
|
||||||
|
and hasattr(variable, "get_format_instructions")
|
||||||
|
):
|
||||||
|
if "handle_keys" not in format_kwargs:
|
||||||
|
format_kwargs["handle_keys"] = []
|
||||||
|
|
||||||
|
# Add the handle_keys to the list
|
||||||
|
format_kwargs["handle_keys"].append(input_variable)
|
||||||
|
|
||||||
|
return prompt, format_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_tool(node_type, class_object: Type[BaseTool], params: Dict):
|
||||||
|
if node_type == "JsonSpec":
|
||||||
|
if file_dict := load_file_into_dict(params.pop("path")):
|
||||||
|
params["dict_"] = file_dict
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid file")
|
||||||
|
return class_object(**params)
|
||||||
|
elif node_type == "PythonFunctionTool":
|
||||||
|
params["func"] = get_function(params.get("code"))
|
||||||
|
return class_object(**params)
|
||||||
|
elif node_type == "PythonFunction":
|
||||||
|
function_string = params["code"]
|
||||||
|
if isinstance(function_string, str):
|
||||||
|
return validate.eval_function(function_string)
|
||||||
|
raise ValueError("Function should be a string")
|
||||||
|
elif node_type.lower() == "tool":
|
||||||
|
return class_object(**params)
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_toolkit(node_type, class_object: Type[BaseToolkit], params: Dict):
|
||||||
|
loaded_toolkit = class_object(**params)
|
||||||
|
# Commenting this out for now to use toolkits as normal tools
|
||||||
|
# if toolkits_creator.has_create_function(node_type):
|
||||||
|
# return load_toolkits_executor(node_type, loaded_toolkit, params)
|
||||||
|
if isinstance(loaded_toolkit, BaseToolkit):
|
||||||
|
return loaded_toolkit.get_tools()
|
||||||
|
return loaded_toolkit
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_embedding(class_object, params: Dict):
|
||||||
|
params.pop("model", None)
|
||||||
|
params.pop("headers", None)
|
||||||
|
try:
|
||||||
|
return class_object(**params)
|
||||||
|
except ValidationError:
|
||||||
|
params = {
|
||||||
|
key: value
|
||||||
|
for key, value in params.items()
|
||||||
|
if key in class_object.__fields__
|
||||||
|
}
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
|
search_kwargs = params.pop("search_kwargs", {})
|
||||||
|
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||||
|
vecstore = initializer(class_object, params)
|
||||||
|
else:
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
vecstore = class_object.from_documents(**params)
|
||||||
|
|
||||||
|
# ! This might not work. Need to test
|
||||||
|
if search_kwargs and hasattr(vecstore, "as_retriever"):
|
||||||
|
vecstore = vecstore.as_retriever(search_kwargs=search_kwargs)
|
||||||
|
|
||||||
|
return vecstore
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_documentloader(class_object: Type[BaseLoader], params: Dict):
|
||||||
|
if "file_filter" in params:
|
||||||
|
# file_filter will be a string but we need a function
|
||||||
|
# that will be used to filter the files using file_filter
|
||||||
|
# like lambda x: x.endswith(".txt") but as we don't know
|
||||||
|
# anything besides the string, we will simply check if the string is
|
||||||
|
# in x and if it is, we will return True
|
||||||
|
file_filter = params.pop("file_filter")
|
||||||
|
extensions = file_filter.split(",")
|
||||||
|
params["file_filter"] = lambda x: any(
|
||||||
|
extension.strip() in x for extension in extensions
|
||||||
|
)
|
||||||
|
metadata = params.pop("metadata", None)
|
||||||
|
if metadata and isinstance(metadata, str):
|
||||||
|
try:
|
||||||
|
metadata = json.loads(metadata)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
"The metadata you provided is not a valid JSON string."
|
||||||
|
) from exc
|
||||||
|
docs = class_object(**params).load()
|
||||||
|
# Now if metadata is an empty dict, we will not add it to the documents
|
||||||
|
if metadata:
|
||||||
|
for doc in docs:
|
||||||
|
# If the document already has metadata, we will not overwrite it
|
||||||
|
if not doc.metadata:
|
||||||
|
doc.metadata = metadata
|
||||||
|
else:
|
||||||
|
doc.metadata.update(metadata)
|
||||||
|
|
||||||
|
return docs
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_textsplitter(
|
||||||
|
class_object,
|
||||||
|
params: Dict,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
documents = params.pop("documents")
|
||||||
|
except KeyError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
"The source you provided did not load correctly or was empty."
|
||||||
|
"Try changing the chunk_size of the Text Splitter."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
if (
|
||||||
|
"separator_type" in params and params["separator_type"] == "Text"
|
||||||
|
) or "separator_type" not in params:
|
||||||
|
params.pop("separator_type", None)
|
||||||
|
# separators might come in as an escaped string like \\n
|
||||||
|
# so we need to convert it to a string
|
||||||
|
if "separators" in params:
|
||||||
|
params["separators"] = (
|
||||||
|
params["separators"].encode().decode("unicode-escape")
|
||||||
|
)
|
||||||
|
text_splitter = class_object(**params)
|
||||||
|
else:
|
||||||
|
from langchain.text_splitter import Language
|
||||||
|
|
||||||
|
language = params.pop("separator_type", None)
|
||||||
|
params["language"] = Language(language)
|
||||||
|
params.pop("separators", None)
|
||||||
|
|
||||||
|
text_splitter = class_object.from_language(**params)
|
||||||
|
return text_splitter.split_documents(documents)
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_utility(node_type, class_object, params: Dict):
|
||||||
|
if node_type == "SQLDatabase":
|
||||||
|
return class_object.from_uri(params.pop("uri"))
|
||||||
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
|
"""Replace ZeroShotPrompt with PromptTemplate"""
|
||||||
|
for node in nodes:
|
||||||
|
if node["data"]["type"] == "ZeroShotPrompt":
|
||||||
|
# Build Prompt Template
|
||||||
|
tools = [
|
||||||
|
tool
|
||||||
|
for tool in nodes
|
||||||
|
if tool["type"] != "chatOutputNode"
|
||||||
|
and "Tool" in tool["data"]["node"]["base_classes"]
|
||||||
|
]
|
||||||
|
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||||
|
break
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
||||||
|
"""Load agent executor from agent class, tools and chain"""
|
||||||
|
allowed_tools: Sequence[BaseTool] = params.get("allowed_tools", [])
|
||||||
|
llm_chain = params["llm_chain"]
|
||||||
|
# agent has hidden args for memory. might need to be support
|
||||||
|
# memory = params["memory"]
|
||||||
|
# if allowed_tools is not a list or set, make it a list
|
||||||
|
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
||||||
|
allowed_tools, BaseTool
|
||||||
|
):
|
||||||
|
allowed_tools = [allowed_tools]
|
||||||
|
tool_names = [tool.name for tool in allowed_tools]
|
||||||
|
# Agent class requires an output_parser but Agent classes
|
||||||
|
# have a default output_parser.
|
||||||
|
agent = agent_class(allowed_tools=tool_names, llm_chain=llm_chain) # type: ignore
|
||||||
|
return AgentExecutor.from_agent_and_tools(
|
||||||
|
agent=agent,
|
||||||
|
tools=allowed_tools,
|
||||||
|
handle_parsing_errors=True,
|
||||||
|
# memory=memory,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_toolkits_executor(node_type: str, toolkit: BaseToolkit, params: dict):
|
||||||
|
create_function: Callable = toolkits_creator.get_create_function(node_type)
|
||||||
|
if llm := params.get("llm"):
|
||||||
|
return create_function(llm=llm, toolkit=toolkit)
|
||||||
|
|
||||||
|
|
||||||
|
def build_prompt_template(prompt, tools):
|
||||||
|
"""Build PromptTemplate from ZeroShotPrompt"""
|
||||||
|
prefix = prompt["node"]["template"]["prefix"]["value"]
|
||||||
|
suffix = prompt["node"]["template"]["suffix"]["value"]
|
||||||
|
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||||
|
|
||||||
|
tool_strings = "\n".join(
|
||||||
|
[
|
||||||
|
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
||||||
|
for tool in tools
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||||
|
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||||
|
value = "\n\n".join([prefix, tool_strings, format_instructions, suffix])
|
||||||
|
|
||||||
|
prompt["type"] = "PromptTemplate"
|
||||||
|
|
||||||
|
prompt["node"] = {
|
||||||
|
"template": {
|
||||||
|
"_type": "prompt",
|
||||||
|
"input_variables": {
|
||||||
|
"type": "str",
|
||||||
|
"required": True,
|
||||||
|
"placeholder": "",
|
||||||
|
"list": True,
|
||||||
|
"show": False,
|
||||||
|
"multiline": False,
|
||||||
|
},
|
||||||
|
"output_parser": {
|
||||||
|
"type": "BaseOutputParser",
|
||||||
|
"required": False,
|
||||||
|
"placeholder": "",
|
||||||
|
"list": False,
|
||||||
|
"show": False,
|
||||||
|
"multline": False,
|
||||||
|
"value": None,
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"type": "str",
|
||||||
|
"required": True,
|
||||||
|
"placeholder": "",
|
||||||
|
"list": False,
|
||||||
|
"show": True,
|
||||||
|
"multiline": True,
|
||||||
|
"value": value,
|
||||||
|
},
|
||||||
|
"template_format": {
|
||||||
|
"type": "str",
|
||||||
|
"required": False,
|
||||||
|
"placeholder": "",
|
||||||
|
"list": False,
|
||||||
|
"show": False,
|
||||||
|
"multline": False,
|
||||||
|
"value": "f-string",
|
||||||
|
},
|
||||||
|
"validate_template": {
|
||||||
|
"type": "bool",
|
||||||
|
"required": False,
|
||||||
|
"placeholder": "",
|
||||||
|
"list": False,
|
||||||
|
"show": False,
|
||||||
|
"multline": False,
|
||||||
|
"value": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "Schema to represent a prompt for an LLM.",
|
||||||
|
"base_classes": ["BasePromptTemplate"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt
|
||||||
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
import json
|
||||||
|
from typing import Any, Callable, Dict, Type
|
||||||
|
from langchain.vectorstores import (
|
||||||
|
Pinecone,
|
||||||
|
Qdrant,
|
||||||
|
Chroma,
|
||||||
|
FAISS,
|
||||||
|
Weaviate,
|
||||||
|
SupabaseVectorStore,
|
||||||
|
MongoDBAtlasVectorSearch,
|
||||||
|
)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def docs_in_params(params: dict) -> bool:
|
||||||
|
"""Check if params has documents OR texts and one of them is not an empty list,
|
||||||
|
If any of them is not an empty list, return True, else return False"""
|
||||||
|
return ("documents" in params and params["documents"]) or (
|
||||||
|
"texts" in params and params["texts"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
||||||
|
"""Initialize mongodb and return the class object"""
|
||||||
|
|
||||||
|
MONGODB_ATLAS_CLUSTER_URI = params.pop("mongodb_atlas_cluster_uri")
|
||||||
|
if not MONGODB_ATLAS_CLUSTER_URI:
|
||||||
|
raise ValueError("Mongodb atlas cluster uri must be provided in the params")
|
||||||
|
from pymongo import MongoClient
|
||||||
|
import certifi
|
||||||
|
|
||||||
|
client: MongoClient = MongoClient(
|
||||||
|
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
|
||||||
|
)
|
||||||
|
db_name = params.pop("db_name", None)
|
||||||
|
collection_name = params.pop("collection_name", None)
|
||||||
|
if not db_name or not collection_name:
|
||||||
|
raise ValueError("db_name and collection_name must be provided in the params")
|
||||||
|
|
||||||
|
index_name = params.pop("index_name", None)
|
||||||
|
if not index_name:
|
||||||
|
raise ValueError("index_name must be provided in the params")
|
||||||
|
|
||||||
|
collection = client[db_name][collection_name]
|
||||||
|
if not docs_in_params(params):
|
||||||
|
# __init__ requires collection, embedding and index_name
|
||||||
|
init_args = {
|
||||||
|
"collection": collection,
|
||||||
|
"index_name": index_name,
|
||||||
|
"embedding": params.get("embedding"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return class_object(**init_args)
|
||||||
|
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
params["collection"] = collection
|
||||||
|
params["index_name"] = index_name
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_supabase(class_object: Type[SupabaseVectorStore], params: dict):
|
||||||
|
"""Initialize supabase and return the class object"""
|
||||||
|
from supabase.client import Client, create_client
|
||||||
|
|
||||||
|
if "supabase_url" not in params or "supabase_service_key" not in params:
|
||||||
|
raise ValueError("Supabase url and service key must be provided in the params")
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
client_kwargs = {
|
||||||
|
"supabase_url": params.pop("supabase_url"),
|
||||||
|
"supabase_key": params.pop("supabase_service_key"),
|
||||||
|
}
|
||||||
|
|
||||||
|
supabase: Client = create_client(**client_kwargs)
|
||||||
|
if not docs_in_params(params):
|
||||||
|
params.pop("documents", None)
|
||||||
|
params.pop("texts", None)
|
||||||
|
return class_object(client=supabase, **params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
|
||||||
|
return class_object.from_documents(client=supabase, **params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_weaviate(class_object: Type[Weaviate], params: dict):
|
||||||
|
"""Initialize weaviate and return the class object"""
|
||||||
|
if not docs_in_params(params):
|
||||||
|
import weaviate # type: ignore
|
||||||
|
|
||||||
|
client_kwargs_json = params.get("client_kwargs", "{}")
|
||||||
|
client_kwargs = json.loads(client_kwargs_json)
|
||||||
|
client_params = {
|
||||||
|
"url": params.get("weaviate_url"),
|
||||||
|
}
|
||||||
|
client_params.update(client_kwargs)
|
||||||
|
weaviate_client = weaviate.Client(**client_params)
|
||||||
|
|
||||||
|
new_params = {
|
||||||
|
"client": weaviate_client,
|
||||||
|
"index_name": params.get("index_name"),
|
||||||
|
"text_key": params.get("text_key"),
|
||||||
|
}
|
||||||
|
return class_object(**new_params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_faiss(class_object: Type[FAISS], params: dict):
|
||||||
|
"""Initialize faiss and return the class object"""
|
||||||
|
|
||||||
|
if not docs_in_params(params):
|
||||||
|
return class_object.load_local
|
||||||
|
|
||||||
|
save_local = params.get("save_local")
|
||||||
|
faiss_index = class_object(**params)
|
||||||
|
if save_local:
|
||||||
|
faiss_index.save_local(folder_path=save_local)
|
||||||
|
return faiss_index
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
|
"""Initialize pinecone and return the class object"""
|
||||||
|
|
||||||
|
import pinecone # type: ignore
|
||||||
|
|
||||||
|
pinecone_api_key = params.get("pinecone_api_key")
|
||||||
|
pinecone_env = params.get("pinecone_env")
|
||||||
|
|
||||||
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
|
if os.getenv("PINECONE_API_KEY") is not None:
|
||||||
|
pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
||||||
|
if os.getenv("PINECONE_ENV") is not None:
|
||||||
|
pinecone_env = os.getenv("PINECONE_ENV")
|
||||||
|
|
||||||
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
|
raise ValueError(
|
||||||
|
"Pinecone API key and environment must be provided in the params"
|
||||||
|
)
|
||||||
|
|
||||||
|
# initialize pinecone
|
||||||
|
pinecone.init(
|
||||||
|
api_key=pinecone_api_key, # find at app.pinecone.io
|
||||||
|
environment=pinecone_env, # next to api key in console
|
||||||
|
)
|
||||||
|
|
||||||
|
# If there are no docs in the params, return an existing index
|
||||||
|
# but first remove any texts or docs keys from the params
|
||||||
|
if not docs_in_params(params):
|
||||||
|
existing_index_params = {
|
||||||
|
"embedding": params.pop("embedding"),
|
||||||
|
}
|
||||||
|
if "index_name" in params:
|
||||||
|
existing_index_params["index_name"] = params.pop("index_name")
|
||||||
|
if "namespace" in params:
|
||||||
|
existing_index_params["namespace"] = params.pop("namespace")
|
||||||
|
|
||||||
|
return class_object.from_existing_index(**existing_index_params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
|
"""Initialize a ChromaDB object from the params"""
|
||||||
|
persist = params.pop("persist", False)
|
||||||
|
if not docs_in_params(params):
|
||||||
|
params.pop("documents", None)
|
||||||
|
params.pop("texts", None)
|
||||||
|
params["embedding_function"] = params.pop("embedding")
|
||||||
|
chromadb = class_object(**params)
|
||||||
|
else:
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
for doc in params["documents"]:
|
||||||
|
if doc.metadata is None:
|
||||||
|
doc.metadata = {}
|
||||||
|
for key, value in doc.metadata.items():
|
||||||
|
if value is None:
|
||||||
|
doc.metadata[key] = ""
|
||||||
|
chromadb = class_object.from_documents(**params)
|
||||||
|
if persist:
|
||||||
|
chromadb.persist()
|
||||||
|
return chromadb
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_qdrant(class_object: Type[Qdrant], params: dict):
|
||||||
|
if not docs_in_params(params):
|
||||||
|
if "location" not in params and "api_key" not in params:
|
||||||
|
raise ValueError("Location and API key must be provided in the params")
|
||||||
|
from qdrant_client import QdrantClient
|
||||||
|
|
||||||
|
client_params = {
|
||||||
|
"location": params.pop("location"),
|
||||||
|
"api_key": params.pop("api_key"),
|
||||||
|
}
|
||||||
|
lc_params = {
|
||||||
|
"collection_name": params.pop("collection_name"),
|
||||||
|
"embeddings": params.pop("embedding"),
|
||||||
|
}
|
||||||
|
client = QdrantClient(**client_params)
|
||||||
|
|
||||||
|
return class_object(client=client, **lc_params)
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
vecstore_initializer: Dict[str, Callable[[Type[Any], dict], Any]] = {
|
||||||
|
"Pinecone": initialize_pinecone,
|
||||||
|
"Chroma": initialize_chroma,
|
||||||
|
"Qdrant": initialize_qdrant,
|
||||||
|
"Weaviate": initialize_weaviate,
|
||||||
|
"FAISS": initialize_faiss,
|
||||||
|
"SupabaseVectorStore": initialize_supabase,
|
||||||
|
"MongoDBAtlasVectorSearch": initialize_mongodb,
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,8 @@ from langflow.interface.tools.base import tool_creator
|
||||||
from langflow.interface.utilities.base import utility_creator
|
from langflow.interface.utilities.base import utility_creator
|
||||||
from langflow.interface.vector_store.base import vectorstore_creator
|
from langflow.interface.vector_store.base import vectorstore_creator
|
||||||
from langflow.interface.wrappers.base import wrapper_creator
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
|
|
||||||
|
|
||||||
def get_type_dict():
|
def get_type_dict():
|
||||||
|
|
@ -28,6 +30,8 @@ def get_type_dict():
|
||||||
"embeddings": embedding_creator.to_list(),
|
"embeddings": embedding_creator.to_list(),
|
||||||
"textSplitters": textsplitter_creator.to_list(),
|
"textSplitters": textsplitter_creator.to_list(),
|
||||||
"utilities": utility_creator.to_list(),
|
"utilities": utility_creator.to_list(),
|
||||||
|
"outputParsers": output_parser_creator.to_list(),
|
||||||
|
"retrievers": retriever_creator.to_list(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,380 +0,0 @@
|
||||||
import json
|
|
||||||
from typing import Any, Callable, Dict, Optional
|
|
||||||
|
|
||||||
from langchain.agents import ZeroShotAgent
|
|
||||||
from langchain.agents import agent as agent_module
|
|
||||||
from langchain.agents.agent import AgentExecutor
|
|
||||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
|
||||||
from langchain.agents.load_tools import (
|
|
||||||
_BASE_TOOLS,
|
|
||||||
_EXTRA_LLM_TOOLS,
|
|
||||||
_EXTRA_OPTIONAL_TOOLS,
|
|
||||||
_LLM_TOOLS,
|
|
||||||
)
|
|
||||||
from langchain.agents.loading import load_agent_from_config
|
|
||||||
from langchain.agents.tools import Tool
|
|
||||||
from langchain.base_language import BaseLanguageModel
|
|
||||||
from langchain.callbacks.base import BaseCallbackManager
|
|
||||||
from langchain.chains.loading import load_chain_from_config
|
|
||||||
from langchain.llms.loading import load_llm_from_config
|
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
from langflow.interface.custom_lists import CUSTOM_NODES
|
|
||||||
from langflow.interface.importing.utils import get_function, import_by_type
|
|
||||||
from langflow.interface.toolkits.base import toolkits_creator
|
|
||||||
from langflow.interface.chains.base import chain_creator
|
|
||||||
from langflow.interface.types import get_type_list
|
|
||||||
from langflow.interface.utils import load_file_into_dict
|
|
||||||
from langflow.utils import util, validate
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
|
||||||
"""Instantiate class from module type and key, and params"""
|
|
||||||
params = convert_params_to_sets(params)
|
|
||||||
params = convert_kwargs(params)
|
|
||||||
if node_type in CUSTOM_NODES:
|
|
||||||
if custom_node := CUSTOM_NODES.get(node_type):
|
|
||||||
if hasattr(custom_node, "initialize"):
|
|
||||||
return custom_node.initialize(**params)
|
|
||||||
return custom_node(**params)
|
|
||||||
|
|
||||||
class_object = import_by_type(_type=base_type, name=node_type)
|
|
||||||
return instantiate_based_on_type(class_object, base_type, node_type, params)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_params_to_sets(params):
|
|
||||||
"""Convert certain params to sets"""
|
|
||||||
if "allowed_special" in params:
|
|
||||||
params["allowed_special"] = set(params["allowed_special"])
|
|
||||||
if "disallowed_special" in params:
|
|
||||||
params["disallowed_special"] = set(params["disallowed_special"])
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
def convert_kwargs(params):
|
|
||||||
# if *kwargs are passed as a string, convert to dict
|
|
||||||
# first find any key that has kwargs in it
|
|
||||||
kwargs_keys = [key for key in params.keys() if "kwargs" in key]
|
|
||||||
for key in kwargs_keys:
|
|
||||||
if isinstance(params[key], str):
|
|
||||||
params[key] = json.loads(params[key])
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_based_on_type(class_object, base_type, node_type, params):
|
|
||||||
if base_type == "agents":
|
|
||||||
return instantiate_agent(class_object, params)
|
|
||||||
elif base_type == "prompts":
|
|
||||||
return instantiate_prompt(node_type, class_object, params)
|
|
||||||
elif base_type == "tools":
|
|
||||||
return instantiate_tool(node_type, class_object, params)
|
|
||||||
elif base_type == "toolkits":
|
|
||||||
return instantiate_toolkit(node_type, class_object, params)
|
|
||||||
elif base_type == "embeddings":
|
|
||||||
return instantiate_embedding(class_object, params)
|
|
||||||
elif base_type == "vectorstores":
|
|
||||||
return instantiate_vectorstore(class_object, params)
|
|
||||||
elif base_type == "documentloaders":
|
|
||||||
return instantiate_documentloader(class_object, params)
|
|
||||||
elif base_type == "textsplitters":
|
|
||||||
return instantiate_textsplitter(class_object, params)
|
|
||||||
elif base_type == "utilities":
|
|
||||||
return instantiate_utility(node_type, class_object, params)
|
|
||||||
elif base_type == "chains":
|
|
||||||
return instantiate_chains(node_type, class_object, params)
|
|
||||||
else:
|
|
||||||
return class_object(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_chains(node_type, class_object, params):
|
|
||||||
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
|
||||||
params["retriever"] = params["retriever"].as_retriever()
|
|
||||||
if node_type in chain_creator.from_method_nodes:
|
|
||||||
method = chain_creator.from_method_nodes[node_type]
|
|
||||||
if class_method := getattr(class_object, method, None):
|
|
||||||
return class_method(**params)
|
|
||||||
raise ValueError(f"Method {method} not found in {class_object}")
|
|
||||||
|
|
||||||
return class_object(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_agent(class_object, params):
|
|
||||||
return load_agent_executor(class_object, params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_prompt(node_type, class_object, params):
|
|
||||||
if node_type == "ZeroShotPrompt":
|
|
||||||
if "tools" not in params:
|
|
||||||
params["tools"] = []
|
|
||||||
return ZeroShotAgent.create_prompt(**params)
|
|
||||||
return class_object(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_tool(node_type, class_object, params):
|
|
||||||
if node_type == "JsonSpec":
|
|
||||||
params["dict_"] = load_file_into_dict(params.pop("path"))
|
|
||||||
return class_object(**params)
|
|
||||||
elif node_type == "PythonFunctionTool":
|
|
||||||
params["func"] = get_function(params.get("code"))
|
|
||||||
return class_object(**params)
|
|
||||||
# For backward compatibility
|
|
||||||
elif node_type == "PythonFunction":
|
|
||||||
function_string = params["code"]
|
|
||||||
if isinstance(function_string, str):
|
|
||||||
return validate.eval_function(function_string)
|
|
||||||
raise ValueError("Function should be a string")
|
|
||||||
elif node_type.lower() == "tool":
|
|
||||||
return class_object(**params)
|
|
||||||
return class_object(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_toolkit(node_type, class_object, params):
|
|
||||||
loaded_toolkit = class_object(**params)
|
|
||||||
# Commenting this out for now to use toolkits as normal tools
|
|
||||||
# if toolkits_creator.has_create_function(node_type):
|
|
||||||
# return load_toolkits_executor(node_type, loaded_toolkit, params)
|
|
||||||
if isinstance(loaded_toolkit, BaseToolkit):
|
|
||||||
return loaded_toolkit.get_tools()
|
|
||||||
return loaded_toolkit
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_embedding(class_object, params):
|
|
||||||
params.pop("model", None)
|
|
||||||
params.pop("headers", None)
|
|
||||||
try:
|
|
||||||
return class_object(**params)
|
|
||||||
except ValidationError:
|
|
||||||
params = {
|
|
||||||
key: value
|
|
||||||
for key, value in params.items()
|
|
||||||
if key in class_object.__fields__
|
|
||||||
}
|
|
||||||
return class_object(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_vectorstore(class_object, params):
|
|
||||||
if len(params.get("documents", [])) == 0:
|
|
||||||
raise ValueError(
|
|
||||||
"The source you provided did not load correctly or was empty."
|
|
||||||
"This may cause an error in the vectorstore."
|
|
||||||
)
|
|
||||||
# Chroma requires all metadata values to not be None
|
|
||||||
if class_object.__name__ == "Chroma":
|
|
||||||
for doc in params["documents"]:
|
|
||||||
if doc.metadata is None:
|
|
||||||
doc.metadata = {}
|
|
||||||
for key, value in doc.metadata.items():
|
|
||||||
if value is None:
|
|
||||||
doc.metadata[key] = ""
|
|
||||||
return class_object.from_documents(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_documentloader(class_object, params):
|
|
||||||
return class_object(**params).load()
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_textsplitter(class_object, params):
|
|
||||||
try:
|
|
||||||
documents = params.pop("documents")
|
|
||||||
except KeyError as e:
|
|
||||||
raise ValueError(
|
|
||||||
"The source you provided did not load correctly or was empty."
|
|
||||||
"Try changing the chunk_size of the Text Splitter."
|
|
||||||
) from e
|
|
||||||
text_splitter = class_object(**params)
|
|
||||||
return text_splitter.split_documents(documents)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_utility(node_type, class_object, params):
|
|
||||||
if node_type == "SQLDatabase":
|
|
||||||
return class_object.from_uri(params.pop("uri"))
|
|
||||||
return class_object(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def replace_zero_shot_prompt_with_prompt_template(nodes):
|
|
||||||
"""Replace ZeroShotPrompt with PromptTemplate"""
|
|
||||||
for node in nodes:
|
|
||||||
if node["data"]["type"] == "ZeroShotPrompt":
|
|
||||||
# Build Prompt Template
|
|
||||||
tools = [
|
|
||||||
tool
|
|
||||||
for tool in nodes
|
|
||||||
if tool["type"] != "chatOutputNode"
|
|
||||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
|
||||||
]
|
|
||||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
|
||||||
break
|
|
||||||
return nodes
|
|
||||||
|
|
||||||
|
|
||||||
def load_langchain_type_from_config(config: Dict[str, Any]):
|
|
||||||
"""Load langchain type from config"""
|
|
||||||
# Get type list
|
|
||||||
type_list = get_type_list()
|
|
||||||
if config["_type"] in type_list["agents"]:
|
|
||||||
config = util.update_verbose(config, new_value=False)
|
|
||||||
return load_agent_executor_from_config(config, verbose=True)
|
|
||||||
elif config["_type"] in type_list["chains"]:
|
|
||||||
config = util.update_verbose(config, new_value=False)
|
|
||||||
return load_chain_from_config(config, verbose=True)
|
|
||||||
elif config["_type"] in type_list["llms"]:
|
|
||||||
config = util.update_verbose(config, new_value=True)
|
|
||||||
return load_llm_from_config(config)
|
|
||||||
else:
|
|
||||||
raise ValueError("Type should be either agent, chain or llm")
|
|
||||||
|
|
||||||
|
|
||||||
def load_agent_executor_from_config(
|
|
||||||
config: dict,
|
|
||||||
llm: Optional[BaseLanguageModel] = None,
|
|
||||||
tools: Optional[list[Tool]] = None,
|
|
||||||
callback_manager: Optional[BaseCallbackManager] = None,
|
|
||||||
**kwargs: Any,
|
|
||||||
):
|
|
||||||
tools = load_tools_from_config(config["allowed_tools"])
|
|
||||||
config["allowed_tools"] = [tool.name for tool in tools] if tools else []
|
|
||||||
agent_obj = load_agent_from_config(config, llm, tools, **kwargs)
|
|
||||||
|
|
||||||
return AgentExecutor.from_agent_and_tools(
|
|
||||||
agent=agent_obj,
|
|
||||||
tools=tools,
|
|
||||||
callback_manager=callback_manager,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
|
||||||
"""Load agent executor from agent class, tools and chain"""
|
|
||||||
allowed_tools = params.get("allowed_tools", [])
|
|
||||||
llm_chain = params["llm_chain"]
|
|
||||||
# if allowed_tools is not a list or set, make it a list
|
|
||||||
if not isinstance(allowed_tools, (list, set)):
|
|
||||||
allowed_tools = [allowed_tools]
|
|
||||||
tool_names = [tool.name for tool in allowed_tools]
|
|
||||||
# Agent class requires an output_parser but Agent classes
|
|
||||||
# have a default output_parser.
|
|
||||||
agent = agent_class(allowed_tools=tool_names, llm_chain=llm_chain) # type: ignore
|
|
||||||
return AgentExecutor.from_agent_and_tools(
|
|
||||||
agent=agent,
|
|
||||||
tools=allowed_tools,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_toolkits_executor(node_type: str, toolkit: BaseToolkit, params: dict):
|
|
||||||
create_function: Callable = toolkits_creator.get_create_function(node_type)
|
|
||||||
if llm := params.get("llm"):
|
|
||||||
return create_function(llm=llm, toolkit=toolkit)
|
|
||||||
|
|
||||||
|
|
||||||
def load_tools_from_config(tool_list: list[dict]) -> list:
|
|
||||||
"""Load tools based on a config list.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: config list.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of tools.
|
|
||||||
"""
|
|
||||||
tools = []
|
|
||||||
for tool in tool_list:
|
|
||||||
tool_type = tool.pop("_type")
|
|
||||||
llm_config = tool.pop("llm", None)
|
|
||||||
llm = load_llm_from_config(llm_config) if llm_config else None
|
|
||||||
kwargs = tool
|
|
||||||
if tool_type in _BASE_TOOLS:
|
|
||||||
tools.append(_BASE_TOOLS[tool_type]())
|
|
||||||
elif tool_type in _LLM_TOOLS:
|
|
||||||
if llm is None:
|
|
||||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
|
||||||
tools.append(_LLM_TOOLS[tool_type](llm))
|
|
||||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
|
||||||
if llm is None:
|
|
||||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
|
||||||
_get_llm_tool_func, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
|
||||||
if missing_keys := set(extra_keys).difference(kwargs):
|
|
||||||
raise ValueError(
|
|
||||||
f"Tool {tool_type} requires some parameters that were not "
|
|
||||||
f"provided: {missing_keys}"
|
|
||||||
)
|
|
||||||
tools.append(_get_llm_tool_func(llm=llm, **kwargs))
|
|
||||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
|
||||||
_get_tool_func, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
|
||||||
kwargs = {k: value for k, value in kwargs.items() if value}
|
|
||||||
tools.append(_get_tool_func(**kwargs))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Got unknown tool {tool_type}")
|
|
||||||
return tools
|
|
||||||
|
|
||||||
|
|
||||||
def build_prompt_template(prompt, tools):
|
|
||||||
"""Build PromptTemplate from ZeroShotPrompt"""
|
|
||||||
prefix = prompt["node"]["template"]["prefix"]["value"]
|
|
||||||
suffix = prompt["node"]["template"]["suffix"]["value"]
|
|
||||||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
|
||||||
|
|
||||||
tool_strings = "\n".join(
|
|
||||||
[
|
|
||||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
|
||||||
for tool in tools
|
|
||||||
]
|
|
||||||
)
|
|
||||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
|
||||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
|
||||||
value = "\n\n".join([prefix, tool_strings, format_instructions, suffix])
|
|
||||||
|
|
||||||
prompt["type"] = "PromptTemplate"
|
|
||||||
|
|
||||||
prompt["node"] = {
|
|
||||||
"template": {
|
|
||||||
"_type": "prompt",
|
|
||||||
"input_variables": {
|
|
||||||
"type": "str",
|
|
||||||
"required": True,
|
|
||||||
"placeholder": "",
|
|
||||||
"list": True,
|
|
||||||
"show": False,
|
|
||||||
"multiline": False,
|
|
||||||
},
|
|
||||||
"output_parser": {
|
|
||||||
"type": "BaseOutputParser",
|
|
||||||
"required": False,
|
|
||||||
"placeholder": "",
|
|
||||||
"list": False,
|
|
||||||
"show": False,
|
|
||||||
"multline": False,
|
|
||||||
"value": None,
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"type": "str",
|
|
||||||
"required": True,
|
|
||||||
"placeholder": "",
|
|
||||||
"list": False,
|
|
||||||
"show": True,
|
|
||||||
"multiline": True,
|
|
||||||
"value": value,
|
|
||||||
},
|
|
||||||
"template_format": {
|
|
||||||
"type": "str",
|
|
||||||
"required": False,
|
|
||||||
"placeholder": "",
|
|
||||||
"list": False,
|
|
||||||
"show": False,
|
|
||||||
"multline": False,
|
|
||||||
"value": "f-string",
|
|
||||||
},
|
|
||||||
"validate_template": {
|
|
||||||
"type": "bool",
|
|
||||||
"required": False,
|
|
||||||
"placeholder": "",
|
|
||||||
"list": False,
|
|
||||||
"show": False,
|
|
||||||
"multline": False,
|
|
||||||
"value": True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"description": "Schema to represent a prompt for an LLM.",
|
|
||||||
"base_classes": ["BasePromptTemplate"],
|
|
||||||
}
|
|
||||||
|
|
||||||
return prompt
|
|
||||||
|
|
@ -6,12 +6,18 @@ from langflow.settings import settings
|
||||||
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
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||||
|
from langflow.custom.customs import get_custom_nodes
|
||||||
|
|
||||||
|
|
||||||
class MemoryCreator(LangChainTypeCreator):
|
class MemoryCreator(LangChainTypeCreator):
|
||||||
type_name: str = "memories"
|
type_name: str = "memories"
|
||||||
|
|
||||||
|
from_method_nodes = {
|
||||||
|
"ZepChatMessageHistory": "__init__",
|
||||||
|
"SQLiteEntityStore": "__init__",
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frontend_node_class(self) -> Type[FrontendNode]:
|
def frontend_node_class(self) -> Type[FrontendNode]:
|
||||||
"""The class type of the FrontendNode created in frontend_node."""
|
"""The class type of the FrontendNode created in frontend_node."""
|
||||||
|
|
@ -26,6 +32,14 @@ class MemoryCreator(LangChainTypeCreator):
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
"""Get the signature of a memory."""
|
"""Get the signature of a memory."""
|
||||||
try:
|
try:
|
||||||
|
if name in get_custom_nodes(self.type_name).keys():
|
||||||
|
return get_custom_nodes(self.type_name)[name]
|
||||||
|
elif name in self.from_method_nodes:
|
||||||
|
return build_template_from_method(
|
||||||
|
name,
|
||||||
|
type_to_cls_dict=memory_type_to_cls_dict,
|
||||||
|
method_name=self.from_method_nodes[name],
|
||||||
|
)
|
||||||
return build_template_from_class(name, memory_type_to_cls_dict)
|
return build_template_from_class(name, memory_type_to_cls_dict)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Memory not found") from exc
|
raise ValueError("Memory not found") from exc
|
||||||
|
|
|
||||||
64
src/backend/langflow/interface/output_parsers/base.py
Normal file
64
src/backend/langflow/interface/output_parsers/base.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
|
from langchain import output_parsers
|
||||||
|
|
||||||
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.interface.importing.utils import import_class
|
||||||
|
from langflow.settings import settings
|
||||||
|
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||||
|
|
||||||
|
|
||||||
|
class OutputParserCreator(LangChainTypeCreator):
|
||||||
|
type_name: str = "output_parsers"
|
||||||
|
from_method_nodes = {
|
||||||
|
"StructuredOutputParser": "from_response_schemas",
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[OutputParserFrontendNode]:
|
||||||
|
return OutputParserFrontendNode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
if self.type_dict is None:
|
||||||
|
self.type_dict = {
|
||||||
|
output_parser_name: import_class(
|
||||||
|
f"langchain.output_parsers.{output_parser_name}"
|
||||||
|
)
|
||||||
|
# if output_parser_name is not lower case it is a class
|
||||||
|
for output_parser_name in output_parsers.__all__
|
||||||
|
}
|
||||||
|
self.type_dict = {
|
||||||
|
name: output_parser
|
||||||
|
for name, output_parser in self.type_dict.items()
|
||||||
|
if name in settings.output_parsers or settings.dev
|
||||||
|
}
|
||||||
|
return self.type_dict
|
||||||
|
|
||||||
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
|
try:
|
||||||
|
if name in self.from_method_nodes:
|
||||||
|
return build_template_from_method(
|
||||||
|
name,
|
||||||
|
type_to_cls_dict=self.type_to_loader_dict,
|
||||||
|
method_name=self.from_method_nodes[name],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return build_template_from_class(
|
||||||
|
name,
|
||||||
|
type_to_cls_dict=self.type_to_loader_dict,
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
# raise ValueError("OutputParser not found") from exc
|
||||||
|
logger.error(f"OutputParser {name} not found: {exc}")
|
||||||
|
except AttributeError as exc:
|
||||||
|
logger.error(f"OutputParser {name} not loaded: {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_list(self) -> List[str]:
|
||||||
|
return list(self.type_to_loader_dict.keys())
|
||||||
|
|
||||||
|
|
||||||
|
output_parser_creator = OutputParserCreator()
|
||||||
0
src/backend/langflow/interface/retrievers/__init__.py
Normal file
0
src/backend/langflow/interface/retrievers/__init__.py
Normal file
58
src/backend/langflow/interface/retrievers/base.py
Normal file
58
src/backend/langflow/interface/retrievers/base.py
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
from typing import Any, Dict, List, Optional, Type
|
||||||
|
|
||||||
|
from langchain import retrievers
|
||||||
|
|
||||||
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.interface.importing.utils import import_class
|
||||||
|
from langflow.settings import settings
|
||||||
|
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from langflow.utils.util import build_template_from_method, build_template_from_class
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieverCreator(LangChainTypeCreator):
|
||||||
|
type_name: str = "retrievers"
|
||||||
|
|
||||||
|
from_method_nodes = {"MultiQueryRetriever": "from_llm", "ZepRetriever": "__init__"}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[RetrieverFrontendNode]:
|
||||||
|
return RetrieverFrontendNode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
if self.type_dict is None:
|
||||||
|
self.type_dict: dict[str, Any] = {
|
||||||
|
retriever_name: import_class(f"langchain.retrievers.{retriever_name}")
|
||||||
|
for retriever_name in retrievers.__all__
|
||||||
|
}
|
||||||
|
return self.type_dict
|
||||||
|
|
||||||
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
|
"""Get the signature of an embedding."""
|
||||||
|
try:
|
||||||
|
if name in self.from_method_nodes:
|
||||||
|
return build_template_from_method(
|
||||||
|
name,
|
||||||
|
type_to_cls_dict=self.type_to_loader_dict,
|
||||||
|
method_name=self.from_method_nodes[name],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return build_template_from_class(
|
||||||
|
name, type_to_cls_dict=self.type_to_loader_dict
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError(f"Retriever {name} not found") from exc
|
||||||
|
except AttributeError as exc:
|
||||||
|
logger.error(f"Retriever {name} not loaded: {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_list(self) -> List[str]:
|
||||||
|
return [
|
||||||
|
retriever
|
||||||
|
for retriever in self.type_to_loader_dict.keys()
|
||||||
|
if retriever in settings.retrievers or settings.dev
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
retriever_creator = RetrieverCreator()
|
||||||
|
|
@ -14,6 +14,23 @@ def build_langchain_object_with_caching(data_graph):
|
||||||
return graph.build()
|
return graph.build()
|
||||||
|
|
||||||
|
|
||||||
|
@memoize_dict(maxsize=10)
|
||||||
|
def build_sorted_vertices_with_caching(data_graph):
|
||||||
|
"""
|
||||||
|
Build langchain object from data_graph.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.debug("Building langchain object")
|
||||||
|
graph = Graph.from_payload(data_graph)
|
||||||
|
sorted_vertices = graph.topological_sort()
|
||||||
|
artifacts = {}
|
||||||
|
for vertex in sorted_vertices:
|
||||||
|
vertex.build()
|
||||||
|
if vertex.artifacts:
|
||||||
|
artifacts.update(vertex.artifacts)
|
||||||
|
return graph.build(), artifacts
|
||||||
|
|
||||||
|
|
||||||
def build_langchain_object(data_graph):
|
def build_langchain_object(data_graph):
|
||||||
"""
|
"""
|
||||||
Build langchain object from data_graph.
|
Build langchain object from data_graph.
|
||||||
|
|
@ -62,6 +79,10 @@ def update_memory_keys(langchain_object, possible_new_mem_key):
|
||||||
if key not in [langchain_object.memory.memory_key, possible_new_mem_key]
|
if key not in [langchain_object.memory.memory_key, possible_new_mem_key]
|
||||||
][0]
|
][0]
|
||||||
|
|
||||||
langchain_object.memory.input_key = input_key
|
keys = [input_key, output_key, possible_new_mem_key]
|
||||||
langchain_object.memory.output_key = output_key
|
attrs = ["input_key", "output_key", "memory_key"]
|
||||||
langchain_object.memory.memory_key = possible_new_mem_key
|
for key, attr in zip(keys, attrs):
|
||||||
|
try:
|
||||||
|
setattr(langchain_object.memory, attr, key)
|
||||||
|
except ValueError as exc:
|
||||||
|
logger.debug(f"{langchain_object.memory} has no attribute {attr} ({exc})")
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ class ToolCreator(LangChainTypeCreator):
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
"""Get the signature of a tool."""
|
"""Get the signature of a tool."""
|
||||||
|
|
||||||
base_classes = ["Tool"]
|
base_classes = ["Tool", "BaseTool"]
|
||||||
fields = []
|
fields = []
|
||||||
params = []
|
params = []
|
||||||
tool_params = {}
|
tool_params = {}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ from langflow.interface.tools.base import tool_creator
|
||||||
from langflow.interface.utilities.base import utility_creator
|
from langflow.interface.utilities.base import utility_creator
|
||||||
from langflow.interface.vector_store.base import vectorstore_creator
|
from langflow.interface.vector_store.base import vectorstore_creator
|
||||||
from langflow.interface.wrappers.base import wrapper_creator
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
|
|
||||||
|
|
||||||
def get_type_list():
|
def get_type_list():
|
||||||
|
|
@ -44,6 +46,8 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
|
||||||
documentloader_creator,
|
documentloader_creator,
|
||||||
textsplitter_creator,
|
textsplitter_creator,
|
||||||
utility_creator,
|
utility_creator,
|
||||||
|
output_parser_creator,
|
||||||
|
retriever_creator,
|
||||||
]
|
]
|
||||||
|
|
||||||
all_types = {}
|
all_types = {}
|
||||||
|
|
@ -52,3 +56,6 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
|
||||||
if created_types[creator.type_name].values():
|
if created_types[creator.type_name].values():
|
||||||
all_types.update(created_types)
|
all_types.update(created_types)
|
||||||
return all_types
|
return all_types
|
||||||
|
|
||||||
|
|
||||||
|
langchain_types_dict = build_langchain_types_dict()
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,27 @@ import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
import yaml
|
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.chat.config import ChatConfig
|
||||||
|
|
||||||
|
|
||||||
def load_file_into_dict(file_path: str) -> dict:
|
def load_file_into_dict(file_path: str) -> dict:
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
raise FileNotFoundError(f"File not found: {file_path}")
|
raise FileNotFoundError(f"File not found: {file_path}")
|
||||||
|
|
||||||
file_extension = os.path.splitext(file_path)[1].lower()
|
# Files names are UUID, so we can't find the extension
|
||||||
|
with open(file_path, "r") as file:
|
||||||
if file_extension == ".json":
|
try:
|
||||||
with open(file_path, "r") as json_file:
|
data = json.load(file)
|
||||||
data = json.load(json_file)
|
except json.JSONDecodeError:
|
||||||
elif file_extension in [".yaml", ".yml"]:
|
file.seek(0)
|
||||||
with open(file_path, "r") as yaml_file:
|
data = yaml.safe_load(file)
|
||||||
data = yaml.safe_load(yaml_file)
|
except ValueError as exc:
|
||||||
else:
|
raise ValueError("Invalid file type. Expected .json or .yaml.") from exc
|
||||||
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -48,9 +49,9 @@ def try_setting_streaming_options(langchain_object, websocket):
|
||||||
|
|
||||||
if isinstance(llm, BaseLanguageModel):
|
if isinstance(llm, BaseLanguageModel):
|
||||||
if hasattr(llm, "streaming") and isinstance(llm.streaming, bool):
|
if hasattr(llm, "streaming") and isinstance(llm.streaming, bool):
|
||||||
llm.streaming = True
|
llm.streaming = ChatConfig.streaming
|
||||||
elif hasattr(llm, "stream") and isinstance(llm.stream, bool):
|
elif hasattr(llm, "stream") and isinstance(llm.stream, bool):
|
||||||
llm.stream = True
|
llm.stream = ChatConfig.streaming
|
||||||
|
|
||||||
return langchain_object
|
return langchain_object
|
||||||
|
|
||||||
|
|
@ -58,3 +59,29 @@ def try_setting_streaming_options(langchain_object, websocket):
|
||||||
def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||||
"""Extract input variables from prompt."""
|
"""Extract input variables from prompt."""
|
||||||
return re.findall(r"{(.*?)}", prompt)
|
return re.findall(r"{(.*?)}", prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_llm_caching():
|
||||||
|
"""Setup LLM caching."""
|
||||||
|
|
||||||
|
from langflow.settings import settings
|
||||||
|
|
||||||
|
try:
|
||||||
|
set_langchain_cache(settings)
|
||||||
|
except ImportError:
|
||||||
|
logger.warning(f"Could not import {settings.cache}. ")
|
||||||
|
except Exception as 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):
|
||||||
|
import langchain
|
||||||
|
from langflow.interface.importing.utils import import_class
|
||||||
|
|
||||||
|
cache_type = os.getenv("LANGFLOW_LANGCHAIN_CACHE")
|
||||||
|
cache_class = import_class(f"langchain.cache.{cache_type or settings.cache}")
|
||||||
|
|
||||||
|
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
||||||
|
langchain.llm_cache = cache_class()
|
||||||
|
logger.info(f"LLM caching setup with {cache_class.__name__}")
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from langchain import requests
|
from langchain import requests, sql_database
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
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, build_template_from_method
|
||||||
|
|
||||||
|
|
||||||
class WrapperCreator(LangChainTypeCreator):
|
class WrapperCreator(LangChainTypeCreator):
|
||||||
type_name: str = "wrappers"
|
type_name: str = "wrappers"
|
||||||
|
|
||||||
|
from_method_nodes = {"SQLDatabase": "from_uri"}
|
||||||
|
|
||||||
@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:
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper]
|
wrapper.__name__: wrapper
|
||||||
|
for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase]
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
|
if name in self.from_method_nodes:
|
||||||
|
return build_template_from_method(
|
||||||
|
name,
|
||||||
|
type_to_cls_dict=self.type_to_loader_dict,
|
||||||
|
add_function=True,
|
||||||
|
method_name=self.from_method_nodes[name],
|
||||||
|
)
|
||||||
|
|
||||||
return build_template_from_class(name, self.type_to_loader_dict)
|
return build_template_from_class(name, self.type_to_loader_dict)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Wrapper not found") from exc
|
raise ValueError("Wrapper not found") from exc
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
# This file is used by lc-serve to load the mounted app and serve it.
|
# This file is used by lc-serve to load the mounted app and serve it.
|
||||||
|
|
||||||
from pathlib import Path
|
import os
|
||||||
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
# Use the JCLOUD_WORKSPACE for db URL if it's provided by JCloud.
|
||||||
|
if "JCLOUD_WORKSPACE" in os.environ:
|
||||||
|
os.environ[
|
||||||
|
"LANGFLOW_DATABASE_URL"
|
||||||
|
] = f"sqlite:///{os.environ['JCLOUD_WORKSPACE']}/langflow.db"
|
||||||
|
|
||||||
from langflow.main import create_app
|
from langflow.main import setup_app
|
||||||
|
from langflow.utils.logger import configure
|
||||||
|
|
||||||
app = create_app()
|
configure(log_level="DEBUG")
|
||||||
path = Path(__file__).parent
|
app = setup_app()
|
||||||
static_files_dir = path / "frontend"
|
|
||||||
app.mount(
|
|
||||||
"/",
|
|
||||||
StaticFiles(directory=static_files_dir, html=True),
|
|
||||||
name="static",
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
from langflow.database.base import create_db_and_tables
|
from langflow.database.base import create_db_and_tables
|
||||||
|
from langflow.interface.utils import setup_llm_caching
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|
@ -28,6 +33,43 @@ def create_app():
|
||||||
|
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.on_event("startup")(create_db_and_tables)
|
app.on_event("startup")(create_db_and_tables)
|
||||||
|
app.on_event("startup")(setup_llm_caching)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def setup_static_files(app: FastAPI, static_files_dir: Path):
|
||||||
|
"""
|
||||||
|
Setup the static files directory.
|
||||||
|
Args:
|
||||||
|
app (FastAPI): FastAPI app.
|
||||||
|
path (str): Path to the static files directory.
|
||||||
|
"""
|
||||||
|
app.mount(
|
||||||
|
"/",
|
||||||
|
StaticFiles(directory=static_files_dir, html=True),
|
||||||
|
name="static",
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.exception_handler(404)
|
||||||
|
async def custom_404_handler(request, __):
|
||||||
|
path = static_files_dir / "index.html"
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
raise RuntimeError(f"File at path {path} does not exist.")
|
||||||
|
return FileResponse(path)
|
||||||
|
|
||||||
|
|
||||||
|
# app = create_app()
|
||||||
|
# setup_static_files(app, static_files_dir)
|
||||||
|
def setup_app(static_files_dir: Optional[Path] = None) -> FastAPI:
|
||||||
|
"""Setup the FastAPI app."""
|
||||||
|
# get the directory of the current file
|
||||||
|
if not static_files_dir:
|
||||||
|
frontend_path = Path(__file__).parent
|
||||||
|
static_files_dir = frontend_path / "frontend"
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
setup_static_files(app, static_files_dir)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Union
|
||||||
from langflow.api.v1.callback import (
|
from langflow.api.v1.callback import (
|
||||||
AsyncStreamingLLMCallbackHandler,
|
AsyncStreamingLLMCallbackHandler,
|
||||||
StreamingLLMCallbackHandler,
|
StreamingLLMCallbackHandler,
|
||||||
|
|
@ -6,39 +7,31 @@ from langflow.processing.process import fix_memory_inputs, format_actions
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
async def get_result_and_steps(langchain_object, message: str, **kwargs):
|
async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwargs):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
langchain_object.verbose = True
|
langchain_object.verbose = True
|
||||||
chat_input = None
|
|
||||||
memory_key = ""
|
|
||||||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
|
||||||
memory_key = langchain_object.memory.memory_key
|
|
||||||
|
|
||||||
if hasattr(langchain_object, "input_keys"):
|
|
||||||
for key in langchain_object.input_keys:
|
|
||||||
if key not in [memory_key, "chat_history"]:
|
|
||||||
chat_input = {key: message}
|
|
||||||
else:
|
|
||||||
chat_input = message # type: ignore
|
|
||||||
|
|
||||||
if hasattr(langchain_object, "return_intermediate_steps"):
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
# https://github.com/hwchase17/langchain/issues/2068
|
||||||
# Deactivating until we have a frontend solution
|
# Deactivating until we have a frontend solution
|
||||||
# to display intermediate steps
|
# to display intermediate steps
|
||||||
langchain_object.return_intermediate_steps = True
|
langchain_object.return_intermediate_steps = True
|
||||||
|
try:
|
||||||
|
fix_memory_inputs(langchain_object)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(exc)
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
|
||||||
try:
|
try:
|
||||||
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
|
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
|
||||||
output = await langchain_object.acall(chat_input, callbacks=async_callbacks)
|
output = await langchain_object.acall(inputs, callbacks=async_callbacks)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
# make the error message more informative
|
# make the error message more informative
|
||||||
logger.debug(f"Error: {str(exc)}")
|
logger.debug(f"Error: {str(exc)}")
|
||||||
sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)]
|
sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)]
|
||||||
output = langchain_object(chat_input, callbacks=sync_callbacks)
|
output = langchain_object(inputs, callbacks=sync_callbacks)
|
||||||
|
|
||||||
intermediate_steps = (
|
intermediate_steps = (
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
|
|
@ -49,7 +42,12 @@ async def get_result_and_steps(langchain_object, message: str, **kwargs):
|
||||||
if isinstance(output, dict)
|
if isinstance(output, dict)
|
||||||
else output
|
else output
|
||||||
)
|
)
|
||||||
thought = format_actions(intermediate_steps) if intermediate_steps else ""
|
try:
|
||||||
|
thought = format_actions(intermediate_steps) if intermediate_steps else ""
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
|
thought = ""
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
raise ValueError(f"Error: {str(exc)}") from exc
|
raise ValueError(f"Error: {str(exc)}") from exc
|
||||||
return result, thought
|
return result, thought
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import contextlib
|
|
||||||
import io
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from langchain.schema import AgentAction
|
from langchain.schema import AgentAction
|
||||||
import json
|
import json
|
||||||
from langflow.interface.run import (
|
from langflow.interface.run import (
|
||||||
build_langchain_object_with_caching,
|
build_sorted_vertices_with_caching,
|
||||||
get_memory_key,
|
get_memory_key,
|
||||||
update_memory_keys,
|
update_memory_keys,
|
||||||
)
|
)
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
|
from langchain.chains.base import Chain
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,7 +22,10 @@ def fix_memory_inputs(langchain_object):
|
||||||
if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
|
if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if langchain_object.memory.memory_key in langchain_object.input_variables:
|
if (
|
||||||
|
hasattr(langchain_object.memory, "memory_key")
|
||||||
|
and langchain_object.memory.memory_key in langchain_object.input_variables
|
||||||
|
):
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
input_variables = (
|
input_variables = (
|
||||||
|
|
@ -55,69 +57,54 @@ def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
|
||||||
return "\n".join(output)
|
return "\n".join(output)
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought(langchain_object, message: str):
|
def get_result_and_thought(langchain_object: Any, inputs: dict):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
langchain_object.verbose = True
|
langchain_object.verbose = True
|
||||||
chat_input = None
|
|
||||||
memory_key = ""
|
|
||||||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
|
||||||
memory_key = langchain_object.memory.memory_key
|
|
||||||
|
|
||||||
if hasattr(langchain_object, "input_keys"):
|
|
||||||
for key in langchain_object.input_keys:
|
|
||||||
if key not in [memory_key, "chat_history"]:
|
|
||||||
chat_input = {key: message}
|
|
||||||
else:
|
|
||||||
chat_input = message # type: ignore
|
|
||||||
|
|
||||||
if hasattr(langchain_object, "return_intermediate_steps"):
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
langchain_object.return_intermediate_steps = True
|
||||||
# Deactivating until we have a frontend solution
|
|
||||||
# to display intermediate steps
|
|
||||||
langchain_object.return_intermediate_steps = False
|
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
fix_memory_inputs(langchain_object)
|
||||||
|
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
try:
|
||||||
try:
|
output = langchain_object(inputs, return_only_outputs=True)
|
||||||
# if hasattr(langchain_object, "acall"):
|
except ValueError as exc:
|
||||||
# output = await langchain_object.acall(chat_input)
|
# make the error message more informative
|
||||||
# else:
|
logger.debug(f"Error: {str(exc)}")
|
||||||
output = langchain_object(chat_input)
|
output = langchain_object.run(inputs)
|
||||||
except ValueError as exc:
|
|
||||||
# make the error message more informative
|
|
||||||
logger.debug(f"Error: {str(exc)}")
|
|
||||||
output = langchain_object.run(chat_input)
|
|
||||||
|
|
||||||
intermediate_steps = (
|
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
|
||||||
)
|
|
||||||
|
|
||||||
result = (
|
|
||||||
output.get(langchain_object.output_keys[0])
|
|
||||||
if isinstance(output, dict)
|
|
||||||
else output
|
|
||||||
)
|
|
||||||
if intermediate_steps:
|
|
||||||
thought = format_actions(intermediate_steps)
|
|
||||||
else:
|
|
||||||
thought = output_buffer.getvalue()
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError(f"Error: {str(exc)}") from exc
|
raise ValueError(f"Error: {str(exc)}") from exc
|
||||||
return result, thought
|
return output
|
||||||
|
|
||||||
|
|
||||||
def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
def get_input_str_if_only_one_input(inputs: dict) -> Optional[str]:
|
||||||
|
"""Get input string if only one input is provided"""
|
||||||
|
return list(inputs.values())[0] if len(inputs) == 1 else None
|
||||||
|
|
||||||
|
|
||||||
|
def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = None):
|
||||||
"""
|
"""
|
||||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||||
with PromptTemplate,then run the graph and return the result and thought.
|
with PromptTemplate,then run the graph and return the result and thought.
|
||||||
"""
|
"""
|
||||||
# Load langchain object
|
# Load langchain object
|
||||||
langchain_object = build_langchain_object_with_caching(data_graph)
|
langchain_object, artifacts = build_sorted_vertices_with_caching(data_graph)
|
||||||
logger.debug("Loaded langchain object")
|
logger.debug("Loaded LangChain object")
|
||||||
|
if inputs is None:
|
||||||
|
inputs = {}
|
||||||
|
|
||||||
|
# Add artifacts to inputs
|
||||||
|
# artifacts can be documents loaded when building
|
||||||
|
# the flow
|
||||||
|
for (
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
) in artifacts.items():
|
||||||
|
if key not in inputs or not inputs[key]:
|
||||||
|
inputs[key] = value
|
||||||
|
|
||||||
if langchain_object is None:
|
if langchain_object is None:
|
||||||
# Raise user facing error
|
# Raise user facing error
|
||||||
|
|
@ -126,30 +113,39 @@ def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
if isinstance(langchain_object, Chain):
|
||||||
result, thought = get_result_and_thought(langchain_object, message)
|
if inputs is None:
|
||||||
logger.debug("Generated result and thought")
|
raise ValueError("Inputs must be provided for a Chain")
|
||||||
return {"result": str(result), "thought": thought.strip()}
|
logger.debug("Generating result and thought")
|
||||||
|
result = get_result_and_thought(langchain_object, inputs)
|
||||||
|
logger.debug("Generated result and thought")
|
||||||
|
elif isinstance(langchain_object, VectorStore):
|
||||||
|
result = langchain_object.search(**inputs)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
f"Unknown langchain_object type: {type(langchain_object).__name__}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(
|
def load_flow_from_json(
|
||||||
input: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
|
flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Load flow from a JSON file or a JSON object.
|
Load flow from a JSON file or a JSON object.
|
||||||
|
|
||||||
:param input: JSON file path or JSON object
|
:param flow: JSON file path or JSON object
|
||||||
:param tweaks: Optional tweaks to be processed
|
:param tweaks: Optional tweaks to be processed
|
||||||
:param build: If True, build the graph, otherwise return the graph object
|
:param build: If True, build the graph, otherwise return the graph object
|
||||||
:return: Langchain object or Graph object depending on the build parameter
|
:return: Langchain object or Graph object depending on the build parameter
|
||||||
"""
|
"""
|
||||||
# If input is a file path, load JSON from the file
|
# If input is a file path, load JSON from the file
|
||||||
if isinstance(input, (str, Path)):
|
if isinstance(flow, (str, Path)):
|
||||||
with open(input, "r", encoding="utf-8") as f:
|
with open(flow, "r", encoding="utf-8") as f:
|
||||||
flow_graph = json.load(f)
|
flow_graph = json.load(f)
|
||||||
# If input is a dictionary, assume it's a JSON object
|
# If input is a dictionary, assume it's a JSON object
|
||||||
elif isinstance(input, dict):
|
elif isinstance(flow, dict):
|
||||||
flow_graph = input
|
flow_graph = flow
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"Input must be either a file path (str) or a JSON object (dict)"
|
"Input must be either a file path (str) or a JSON object (dict)"
|
||||||
|
|
@ -206,7 +202,8 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
for tweak_name, tweak_value in node_tweaks.items():
|
for tweak_name, tweak_value in node_tweaks.items():
|
||||||
if tweak_name and tweak_value and tweak_name in template_data:
|
if tweak_name and tweak_value and tweak_name in template_data:
|
||||||
template_data[tweak_name]["value"] = tweak_value
|
key = tweak_name if tweak_name == "file_path" else "value"
|
||||||
|
template_data[tweak_name][key] = tweak_value
|
||||||
|
|
||||||
|
|
||||||
def process_tweaks(
|
def process_tweaks(
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,48 @@
|
||||||
import os
|
import os
|
||||||
from typing import List
|
from typing import Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from pydantic import BaseSettings, root_validator
|
from pydantic import BaseSettings, root_validator
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
chains: List[str] = []
|
chains: dict = {}
|
||||||
agents: List[str] = []
|
agents: dict = {}
|
||||||
prompts: List[str] = []
|
prompts: dict = {}
|
||||||
llms: List[str] = []
|
llms: dict = {}
|
||||||
tools: List[str] = []
|
tools: dict = {}
|
||||||
memories: List[str] = []
|
memories: dict = {}
|
||||||
embeddings: List[str] = []
|
embeddings: dict = {}
|
||||||
vectorstores: List[str] = []
|
vectorstores: dict = {}
|
||||||
documentloaders: List[str] = []
|
documentloaders: dict = {}
|
||||||
wrappers: List[str] = []
|
wrappers: dict = {}
|
||||||
toolkits: List[str] = []
|
retrievers: dict = {}
|
||||||
textsplitters: List[str] = []
|
toolkits: dict = {}
|
||||||
utilities: List[str] = []
|
textsplitters: dict = {}
|
||||||
|
utilities: dict = {}
|
||||||
|
output_parsers: dict = {}
|
||||||
dev: bool = False
|
dev: bool = False
|
||||||
database_url: str = "sqlite:///./langflow.db"
|
database_url: Optional[str] = None
|
||||||
|
cache: str = "InMemoryCache"
|
||||||
remove_api_keys: bool = False
|
remove_api_keys: bool = False
|
||||||
|
|
||||||
|
@root_validator(pre=True)
|
||||||
|
def set_database_url(cls, values):
|
||||||
|
if "database_url" not in values:
|
||||||
|
logger.debug(
|
||||||
|
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
|
||||||
|
)
|
||||||
|
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
||||||
|
values["database_url"] = langflow_database_url
|
||||||
|
else:
|
||||||
|
logger.debug("No DATABASE_URL env variable, using sqlite database")
|
||||||
|
values["database_url"] = "sqlite:///./langflow.db"
|
||||||
|
return values
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
validate_assignment = True
|
validate_assignment = True
|
||||||
extra = "ignore"
|
extra = "ignore"
|
||||||
env_prefix = "LANGFLOW_"
|
|
||||||
|
|
||||||
@root_validator(allow_reuse=True)
|
@root_validator(allow_reuse=True)
|
||||||
def validate_lists(cls, values):
|
def validate_lists(cls, values):
|
||||||
|
|
@ -37,16 +53,21 @@ class Settings(BaseSettings):
|
||||||
|
|
||||||
def update_from_yaml(self, file_path: str, dev: bool = False):
|
def update_from_yaml(self, file_path: str, dev: bool = False):
|
||||||
new_settings = load_settings_from_yaml(file_path)
|
new_settings = load_settings_from_yaml(file_path)
|
||||||
self.chains = new_settings.chains or []
|
self.chains = new_settings.chains or {}
|
||||||
self.agents = new_settings.agents or []
|
self.agents = new_settings.agents or {}
|
||||||
self.prompts = new_settings.prompts or []
|
self.prompts = new_settings.prompts or {}
|
||||||
self.llms = new_settings.llms or []
|
self.llms = new_settings.llms or {}
|
||||||
self.tools = new_settings.tools or []
|
self.tools = new_settings.tools or {}
|
||||||
self.memories = new_settings.memories or []
|
self.memories = new_settings.memories or {}
|
||||||
self.wrappers = new_settings.wrappers or []
|
self.wrappers = new_settings.wrappers or {}
|
||||||
self.toolkits = new_settings.toolkits or []
|
self.toolkits = new_settings.toolkits or {}
|
||||||
self.textsplitters = new_settings.textsplitters or []
|
self.textsplitters = new_settings.textsplitters or {}
|
||||||
self.utilities = new_settings.utilities or []
|
self.utilities = new_settings.utilities or {}
|
||||||
|
self.embeddings = new_settings.embeddings or {}
|
||||||
|
self.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.dev = dev
|
self.dev = dev
|
||||||
|
|
||||||
def update_settings(self, **kwargs):
|
def update_settings(self, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
suffixes: list[str] = []
|
suffixes: list[str] = []
|
||||||
fileTypes: list[str] = []
|
fileTypes: list[str] = []
|
||||||
file_types: list[str] = []
|
file_types: list[str] = []
|
||||||
content: Union[str, None] = None
|
file_path: Union[str, None] = None
|
||||||
password: bool = False
|
password: bool = False
|
||||||
options: list[str] = []
|
options: list[str] = []
|
||||||
name: str = ""
|
name: str = ""
|
||||||
display_name: Optional[str] = None
|
display_name: Optional[str] = None
|
||||||
advanced: bool = False
|
advanced: bool = False
|
||||||
|
input_types: list[str] = []
|
||||||
|
info: Optional[str] = ""
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
result = self.dict()
|
result = self.dict()
|
||||||
|
|
@ -35,7 +37,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
result["fileTypes"] = result.pop("file_types")
|
result["fileTypes"] = result.pop("file_types")
|
||||||
|
|
||||||
if self.field_type == "file":
|
if self.field_type == "file":
|
||||||
result["content"] = self.content
|
result["file_path"] = self.file_path
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,16 @@ NON_CHAT_AGENTS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AgentFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
if field.name in ["suffix", "prefix"]:
|
||||||
|
field.show = True
|
||||||
|
if field.name == "Tools" and name == "ZeroShotAgent":
|
||||||
|
field.field_type = "BaseTool"
|
||||||
|
field.is_list = True
|
||||||
|
|
||||||
|
|
||||||
class SQLAgentNode(FrontendNode):
|
class SQLAgentNode(FrontendNode):
|
||||||
name: str = "SQLAgent"
|
name: str = "SQLAgent"
|
||||||
template: Template = Template(
|
template: Template = Template(
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,45 @@
|
||||||
|
from collections import defaultdict
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from langflow.template.frontend_node.formatter import field_formatters
|
||||||
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
|
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
|
||||||
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 constants
|
from langflow.utils import constants
|
||||||
|
|
||||||
|
CLASSES_TO_REMOVE = ["Serializable", "BaseModel", "object"]
|
||||||
|
|
||||||
|
|
||||||
|
class FieldFormatters(BaseModel):
|
||||||
|
formatters = {
|
||||||
|
"openai_api_key": field_formatters.OpenAIAPIKeyFormatter(),
|
||||||
|
}
|
||||||
|
base_formatters = {
|
||||||
|
"kwargs": field_formatters.KwargsFormatter(),
|
||||||
|
"optional": field_formatters.RemoveOptionalFormatter(),
|
||||||
|
"list": field_formatters.ListTypeFormatter(),
|
||||||
|
"dict": field_formatters.DictTypeFormatter(),
|
||||||
|
"union": field_formatters.UnionTypeFormatter(),
|
||||||
|
"multiline": field_formatters.MultilineFieldFormatter(),
|
||||||
|
"show": field_formatters.ShowFieldFormatter(),
|
||||||
|
"password": field_formatters.PasswordFieldFormatter(),
|
||||||
|
"default": field_formatters.DefaultValueFormatter(),
|
||||||
|
"headers": field_formatters.HeadersDefaultValueFormatter(),
|
||||||
|
"dict_code_file": field_formatters.DictCodeFileFormatter(),
|
||||||
|
"model_fields": field_formatters.ModelSpecificFieldFormatter(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
for key, formatter in self.base_formatters.items():
|
||||||
|
formatter.format(field, name)
|
||||||
|
|
||||||
|
for key, formatter in self.formatters.items():
|
||||||
|
if key == field.name:
|
||||||
|
formatter.format(field, name)
|
||||||
|
|
||||||
|
|
||||||
class FrontendNode(BaseModel):
|
class FrontendNode(BaseModel):
|
||||||
template: Template
|
template: Template
|
||||||
|
|
@ -15,14 +47,41 @@ class FrontendNode(BaseModel):
|
||||||
base_classes: List[str]
|
base_classes: List[str]
|
||||||
name: str = ""
|
name: str = ""
|
||||||
display_name: str = ""
|
display_name: str = ""
|
||||||
|
documentation: str = ""
|
||||||
|
custom_fields: defaultdict = defaultdict(list)
|
||||||
|
output_types: List[str] = []
|
||||||
|
field_formatters: FieldFormatters = Field(default_factory=FieldFormatters)
|
||||||
|
|
||||||
|
def process_base_classes(self) -> None:
|
||||||
|
"""Removes unwanted base classes from the list of base classes."""
|
||||||
|
self.base_classes = [
|
||||||
|
base_class
|
||||||
|
for base_class in self.base_classes
|
||||||
|
if base_class not in CLASSES_TO_REMOVE
|
||||||
|
]
|
||||||
|
|
||||||
|
# field formatters is an instance attribute but it is not used in the class
|
||||||
|
# so we need to create a method to get it
|
||||||
|
@staticmethod
|
||||||
|
def get_field_formatters() -> FieldFormatters:
|
||||||
|
return FieldFormatters()
|
||||||
|
|
||||||
|
def set_documentation(self, documentation: str) -> None:
|
||||||
|
"""Sets the documentation of the frontend node."""
|
||||||
|
self.documentation = documentation
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
|
"""Returns a dict representation of the frontend node."""
|
||||||
|
self.process_base_classes()
|
||||||
return {
|
return {
|
||||||
self.name: {
|
self.name: {
|
||||||
"template": self.template.to_dict(self.format_field),
|
"template": self.template.to_dict(self.format_field),
|
||||||
"description": self.description,
|
"description": self.description,
|
||||||
"base_classes": self.base_classes,
|
"base_classes": self.base_classes,
|
||||||
"display_name": self.display_name or self.name,
|
"display_name": self.display_name or self.name,
|
||||||
|
"custom_fields": self.custom_fields,
|
||||||
|
"output_types": self.output_types,
|
||||||
|
"documentation": self.documentation,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,33 +94,8 @@ class FrontendNode(BaseModel):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
"""Formats a given field based on its attributes and value."""
|
"""Formats a given field based on its attributes and value."""
|
||||||
SPECIAL_FIELD_HANDLERS = {
|
|
||||||
"allowed_tools": lambda field: "Tool",
|
|
||||||
"max_value_length": lambda field: "int",
|
|
||||||
}
|
|
||||||
|
|
||||||
key = field.name
|
FrontendNode.get_field_formatters().format(field, name)
|
||||||
value = field.to_dict()
|
|
||||||
_type = value["type"]
|
|
||||||
|
|
||||||
_type = FrontendNode.remove_optional(_type)
|
|
||||||
_type, is_list = FrontendNode.check_for_list_type(_type)
|
|
||||||
field.is_list = is_list or field.is_list
|
|
||||||
_type = FrontendNode.replace_mapping_with_dict(_type)
|
|
||||||
_type = FrontendNode.handle_union_type(_type)
|
|
||||||
|
|
||||||
field.field_type = FrontendNode.handle_special_field(
|
|
||||||
field, key, _type, SPECIAL_FIELD_HANDLERS
|
|
||||||
)
|
|
||||||
field.field_type = FrontendNode.handle_dict_type(field, _type)
|
|
||||||
field.show = FrontendNode.should_show_field(key, field.required)
|
|
||||||
field.password = FrontendNode.should_be_password(key, field.show)
|
|
||||||
field.multiline = FrontendNode.should_be_multiline(key)
|
|
||||||
|
|
||||||
FrontendNode.replace_default_value(field, value)
|
|
||||||
FrontendNode.handle_specific_field_values(field, key, name)
|
|
||||||
FrontendNode.handle_kwargs_field(field)
|
|
||||||
FrontendNode.handle_api_key_field(field, key)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove_optional(_type: str) -> str:
|
def remove_optional(_type: str) -> str:
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,46 @@ class ChainFrontendNode(FrontendNode):
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
field_type="BaseChatMemory",
|
field_type="BaseChatMemory",
|
||||||
required=False,
|
required=True,
|
||||||
show=True,
|
show=True,
|
||||||
name="memory",
|
name="memory",
|
||||||
advanced=False,
|
advanced=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# add return_source_documents
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="return_source_documents",
|
||||||
|
advanced=False,
|
||||||
|
value=True,
|
||||||
|
display_name="Return source documents",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
is_list=True,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
options=QA_CHAIN_TYPES,
|
||||||
|
value=QA_CHAIN_TYPES[0],
|
||||||
|
name="chain_type",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
FrontendNode.format_field(field, name)
|
FrontendNode.format_field(field, name)
|
||||||
|
|
||||||
|
if "name" == "RetrievalQA" and field.name == "memory":
|
||||||
|
field.show = False
|
||||||
|
field.required = False
|
||||||
|
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if "key" in field.name:
|
if "key" in field.name:
|
||||||
field.password = False
|
field.password = False
|
||||||
|
|
@ -47,18 +76,24 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "memory":
|
if field.name == "memory":
|
||||||
field.required = False
|
# field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "verbose":
|
if field.name == "verbose":
|
||||||
field.required = False
|
field.required = False
|
||||||
field.show = True
|
field.show = False
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
if field.name == "llm":
|
if field.name == "llm":
|
||||||
field.required = True
|
field.required = True
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
|
if field.name == "return_source_documents":
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
field.value = True
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterChainNode(FrontendNode):
|
class SeriesCharacterChainNode(FrontendNode):
|
||||||
name: str = "SeriesCharacterChain"
|
name: str = "SeriesCharacterChain"
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,33 @@ You are a good listener and you can talk about anything.
|
||||||
HUMAN_PROMPT = "{input}"
|
HUMAN_PROMPT = "{input}"
|
||||||
|
|
||||||
QA_CHAIN_TYPES = ["stuff", "map_reduce", "map_rerank", "refine"]
|
QA_CHAIN_TYPES = ["stuff", "map_reduce", "map_rerank", "refine"]
|
||||||
|
|
||||||
|
CTRANSFORMERS_DEFAULT_CONFIG = {
|
||||||
|
"top_k": 40,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"temperature": 0.8,
|
||||||
|
"repetition_penalty": 1.1,
|
||||||
|
"last_n_tokens": 64,
|
||||||
|
"seed": -1,
|
||||||
|
"max_new_tokens": 256,
|
||||||
|
"stop": None,
|
||||||
|
"stream": False,
|
||||||
|
"reset": True,
|
||||||
|
"batch_size": 8,
|
||||||
|
"threads": -1,
|
||||||
|
"context_length": -1,
|
||||||
|
"gpu_layers": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
# This variable is used to tell the user
|
||||||
|
# that it can be changed to use other APIs
|
||||||
|
# like Prem and LocalAI
|
||||||
|
OPENAI_API_BASE_INFO = """
|
||||||
|
The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.
|
||||||
|
|
||||||
|
You can change this to use other APIs like JinaChat, LocalAI and Prem.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
INPUT_KEY_INFO = """The variable to be used as Chat Input when more than one variable is available."""
|
||||||
|
OUTPUT_KEY_INFO = """The variable to be used as Chat Output (e.g. answer in a ConversationalRetrievalChain)"""
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
from typing import Optional
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
def build_template(
|
def build_file_field(
|
||||||
suffixes: list, fileTypes: list, name: str = "file_path"
|
suffixes: list, fileTypes: list, name: str = "file_path"
|
||||||
) -> TemplateField:
|
) -> TemplateField:
|
||||||
"""Build a template field for a document loader."""
|
"""Build a template field for a document loader."""
|
||||||
|
|
@ -18,34 +19,39 @@ def build_template(
|
||||||
|
|
||||||
|
|
||||||
class DocumentLoaderFrontNode(FrontendNode):
|
class DocumentLoaderFrontNode(FrontendNode):
|
||||||
|
def add_extra_base_classes(self) -> None:
|
||||||
|
self.base_classes = ["Document"]
|
||||||
|
self.output_types = ["Document"]
|
||||||
|
|
||||||
file_path_templates = {
|
file_path_templates = {
|
||||||
"AirbyteJSONLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"CoNLLULoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"CSVLoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
"CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"UnstructuredEmailLoader": build_template(suffixes=[".eml"], fileTypes=["eml"]),
|
"UnstructuredEmailLoader": build_file_field(
|
||||||
"EverNoteLoader": build_template(suffixes=[".xml"], fileTypes=["xml"]),
|
suffixes=[".eml"], fileTypes=["eml"]
|
||||||
"FacebookChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
),
|
||||||
"GutenbergLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
"SlackDirectoryLoader": build_file_field(suffixes=[".zip"], fileTypes=["zip"]),
|
||||||
"BSHTMLLoader": build_template(suffixes=[".html"], fileTypes=["html"]),
|
"EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]),
|
||||||
"UnstructuredHTMLLoader": build_template(
|
"FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
|
"BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
||||||
|
"UnstructuredHTMLLoader": build_file_field(
|
||||||
suffixes=[".html"], fileTypes=["html"]
|
suffixes=[".html"], fileTypes=["html"]
|
||||||
),
|
),
|
||||||
"UnstructuredImageLoader": build_template(
|
"UnstructuredImageLoader": build_file_field(
|
||||||
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
||||||
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
||||||
),
|
),
|
||||||
"UnstructuredMarkdownLoader": build_template(
|
"UnstructuredMarkdownLoader": build_file_field(
|
||||||
suffixes=[".md"], fileTypes=["md"]
|
suffixes=[".md"], fileTypes=["md"]
|
||||||
),
|
),
|
||||||
"PyPDFLoader": build_template(suffixes=[".pdf"], fileTypes=["pdf"]),
|
"PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]),
|
||||||
"UnstructuredPowerPointLoader": build_template(
|
"UnstructuredPowerPointLoader": build_file_field(
|
||||||
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
||||||
),
|
),
|
||||||
"SlackDirectoryLoader": build_template(suffixes=[".zip"], fileTypes=["zip"]),
|
"SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]),
|
||||||
"SRTLoader": build_template(suffixes=[".srt"], fileTypes=["srt"]),
|
"TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"TelegramChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
"TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||||
"TextLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
"UnstructuredWordDocumentLoader": build_file_field(
|
||||||
"UnstructuredWordDocumentLoader": build_template(
|
|
||||||
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +59,54 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
name = None
|
name = None
|
||||||
display_name = "Web Page"
|
display_name = "Web Page"
|
||||||
if self.template.type_name in self.file_path_templates:
|
if self.template.type_name in {"GitLoader"}:
|
||||||
|
# Add fields repo_path, clone_url, branch and file_filter
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="repo_path",
|
||||||
|
value="",
|
||||||
|
display_name="Path to repository",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="clone_url",
|
||||||
|
value="",
|
||||||
|
display_name="Clone URL",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="branch",
|
||||||
|
value="",
|
||||||
|
display_name="Branch",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="file_filter",
|
||||||
|
value="",
|
||||||
|
display_name="File extensions (comma-separated)",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif self.template.type_name in self.file_path_templates:
|
||||||
self.template.add_field(self.file_path_templates[self.template.type_name])
|
self.template.add_field(self.file_path_templates[self.template.type_name])
|
||||||
elif self.template.type_name in {
|
elif self.template.type_name in {
|
||||||
"WebBaseLoader",
|
"WebBaseLoader",
|
||||||
|
|
@ -62,24 +115,151 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
"HNLoader",
|
"HNLoader",
|
||||||
"IFixitLoader",
|
"IFixitLoader",
|
||||||
"IMSDbLoader",
|
"IMSDbLoader",
|
||||||
|
"GutenbergLoader",
|
||||||
}:
|
}:
|
||||||
name = "web_path"
|
name = "web_path"
|
||||||
|
elif self.template.type_name in {"GutenbergLoader"}:
|
||||||
|
name = "file_path"
|
||||||
elif self.template.type_name in {"GitbookLoader"}:
|
elif self.template.type_name in {"GitbookLoader"}:
|
||||||
name = "web_page"
|
name = "web_page"
|
||||||
elif self.template.type_name in {
|
elif self.template.type_name in {
|
||||||
"NotionDirectoryLoader",
|
"DirectoryLoader",
|
||||||
"ReadTheDocsLoader",
|
"ReadTheDocsLoader",
|
||||||
|
"NotionDirectoryLoader",
|
||||||
|
"PyPDFDirectoryLoader",
|
||||||
}:
|
}:
|
||||||
name = "path"
|
name = "path"
|
||||||
display_name = "Local directory"
|
display_name = "Local directory"
|
||||||
if name:
|
if name:
|
||||||
self.template.add_field(
|
if self.template.type_name in {"DirectoryLoader"}:
|
||||||
TemplateField(
|
for field in build_directory_loader_fields():
|
||||||
field_type="str",
|
self.template.add_field(field)
|
||||||
required=True,
|
else:
|
||||||
show=True,
|
self.template.add_field(
|
||||||
name=name,
|
TemplateField(
|
||||||
value="",
|
field_type="str",
|
||||||
display_name=display_name,
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name=name,
|
||||||
|
value="",
|
||||||
|
display_name=display_name,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
# add a metadata field of type dict
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="code",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="metadata",
|
||||||
|
value="{}",
|
||||||
|
display_name="Metadata",
|
||||||
|
multiline=False,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
if field.name == "metadata":
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.show = True
|
||||||
|
|
||||||
|
|
||||||
|
def build_directory_loader_fields():
|
||||||
|
# if loader_kwargs is None:
|
||||||
|
# loader_kwargs = {}
|
||||||
|
# self.path = path
|
||||||
|
# self.glob = glob
|
||||||
|
# self.load_hidden = load_hidden
|
||||||
|
# self.loader_cls = loader_cls
|
||||||
|
# self.loader_kwargs = loader_kwargs
|
||||||
|
# self.silent_errors = silent_errors
|
||||||
|
# self.recursive = recursive
|
||||||
|
# self.show_progress = show_progress
|
||||||
|
# self.use_multithreading = use_multithreading
|
||||||
|
# self.max_concurrency = max_concurrency
|
||||||
|
# Based on the above fields, we can build the following fields:
|
||||||
|
# path, glob, load_hidden, silent_errors, recursive, show_progress, use_multithreading, max_concurrency
|
||||||
|
# path
|
||||||
|
path = TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="path",
|
||||||
|
value="",
|
||||||
|
display_name="Local directory",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
# glob
|
||||||
|
glob = TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="glob",
|
||||||
|
value="**/*.txt",
|
||||||
|
display_name="glob",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
# load_hidden
|
||||||
|
load_hidden = TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="load_hidden",
|
||||||
|
value="False",
|
||||||
|
display_name="Load hidden files",
|
||||||
|
advanced=True,
|
||||||
|
)
|
||||||
|
# silent_errors
|
||||||
|
silent_errors = TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="silent_errors",
|
||||||
|
value="False",
|
||||||
|
display_name="Silent errors",
|
||||||
|
advanced=True,
|
||||||
|
)
|
||||||
|
# recursive
|
||||||
|
recursive = TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="recursive",
|
||||||
|
value="True",
|
||||||
|
display_name="Recursive",
|
||||||
|
advanced=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# use_multithreading
|
||||||
|
use_multithreading = TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="use_multithreading",
|
||||||
|
value="True",
|
||||||
|
display_name="Use multithreading",
|
||||||
|
advanced=True,
|
||||||
|
)
|
||||||
|
# max_concurrency
|
||||||
|
max_concurrency = TemplateField(
|
||||||
|
field_type="int",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="max_concurrency",
|
||||||
|
value=10,
|
||||||
|
display_name="Max concurrency",
|
||||||
|
advanced=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
path,
|
||||||
|
glob,
|
||||||
|
load_hidden,
|
||||||
|
silent_errors,
|
||||||
|
recursive,
|
||||||
|
use_multithreading,
|
||||||
|
max_concurrency,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class FieldFormatter(BaseModel, ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def format(self, field: TemplateField, name: Optional[str]) -> None:
|
||||||
|
pass
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
|
||||||
|
from langflow.template.frontend_node.formatter.base import FieldFormatter
|
||||||
|
import re
|
||||||
|
|
||||||
|
from langflow.utils.constants import (
|
||||||
|
ANTHROPIC_MODELS,
|
||||||
|
CHAT_OPENAI_MODELS,
|
||||||
|
OPENAI_MODELS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAIAPIKeyFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
if "api_key" in field.name and "OpenAI" in str(name):
|
||||||
|
field.display_name = "OpenAI API Key"
|
||||||
|
field.required = False
|
||||||
|
if field.value is None:
|
||||||
|
field.value = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ModelSpecificFieldFormatter(FieldFormatter):
|
||||||
|
MODEL_DICT = {
|
||||||
|
"OpenAI": OPENAI_MODELS,
|
||||||
|
"ChatOpenAI": CHAT_OPENAI_MODELS,
|
||||||
|
"Anthropic": ANTHROPIC_MODELS,
|
||||||
|
"ChatAnthropic": ANTHROPIC_MODELS,
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
if name in self.MODEL_DICT and field.name == "model_name":
|
||||||
|
field.options = self.MODEL_DICT[name]
|
||||||
|
field.is_list = True
|
||||||
|
|
||||||
|
|
||||||
|
class KwargsFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
if "kwargs" in field.name.lower():
|
||||||
|
field.advanced = True
|
||||||
|
field.required = False
|
||||||
|
field.show = False
|
||||||
|
|
||||||
|
|
||||||
|
class APIKeyFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
if "api" in field.name.lower() and "key" in field.name.lower():
|
||||||
|
field.required = False
|
||||||
|
field.advanced = False
|
||||||
|
|
||||||
|
field.display_name = field.name.replace("_", " ").title()
|
||||||
|
field.display_name = field.display_name.replace("Api", "API")
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveOptionalFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
_type = field.field_type
|
||||||
|
field.field_type = re.sub(r"Optional\[(.*)\]", r"\1", _type)
|
||||||
|
|
||||||
|
|
||||||
|
class ListTypeFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
_type = field.field_type
|
||||||
|
is_list = "List" in _type or "Sequence" in _type
|
||||||
|
if is_list:
|
||||||
|
_type = re.sub(r"(List|Sequence)\[(.*)\]", r"\2", _type)
|
||||||
|
field.is_list = True
|
||||||
|
field.field_type = _type
|
||||||
|
|
||||||
|
|
||||||
|
class DictTypeFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
_type = field.field_type
|
||||||
|
_type = _type.replace("Mapping", "dict")
|
||||||
|
field.field_type = _type
|
||||||
|
|
||||||
|
|
||||||
|
class UnionTypeFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
_type = field.field_type
|
||||||
|
if "Union" in _type:
|
||||||
|
_type = _type.replace("Union[", "")[:-1]
|
||||||
|
_type = _type.split(",")[0]
|
||||||
|
_type = _type.replace("]", "").replace("[", "")
|
||||||
|
field.field_type = _type
|
||||||
|
|
||||||
|
|
||||||
|
class SpecialFieldFormatter(FieldFormatter):
|
||||||
|
SPECIAL_FIELD_HANDLERS = {
|
||||||
|
"allowed_tools": lambda field: "Tool",
|
||||||
|
"max_value_length": lambda field: "int",
|
||||||
|
}
|
||||||
|
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
handler = self.SPECIAL_FIELD_HANDLERS.get(field.name)
|
||||||
|
field.field_type = handler(field) if handler else field.field_type
|
||||||
|
|
||||||
|
|
||||||
|
class ShowFieldFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
key = field.name
|
||||||
|
required = field.required
|
||||||
|
field.show = (
|
||||||
|
(required and key not in ["input_variables"])
|
||||||
|
or key in FORCE_SHOW_FIELDS
|
||||||
|
or "api" in key
|
||||||
|
or ("key" in key and "input" not in key and "output" not in key)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordFieldFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
key = field.name
|
||||||
|
show = field.show
|
||||||
|
if (
|
||||||
|
any(text in key.lower() for text in {"password", "token", "api", "key"})
|
||||||
|
and show
|
||||||
|
):
|
||||||
|
field.password = True
|
||||||
|
|
||||||
|
|
||||||
|
class MultilineFieldFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
key = field.name
|
||||||
|
if key in {
|
||||||
|
"suffix",
|
||||||
|
"prefix",
|
||||||
|
"template",
|
||||||
|
"examples",
|
||||||
|
"code",
|
||||||
|
"headers",
|
||||||
|
"description",
|
||||||
|
}:
|
||||||
|
field.multiline = True
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultValueFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
value = field.to_dict()
|
||||||
|
if "default" in value:
|
||||||
|
field.value = value["default"]
|
||||||
|
|
||||||
|
|
||||||
|
class HeadersDefaultValueFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
key = field.name
|
||||||
|
if key == "headers":
|
||||||
|
field.value = """{'Authorization': 'Bearer <token>'}"""
|
||||||
|
|
||||||
|
|
||||||
|
class DictCodeFileFormatter(FieldFormatter):
|
||||||
|
def format(self, field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
key = field.name
|
||||||
|
value = field.to_dict()
|
||||||
|
_type = value["type"]
|
||||||
|
if "dict" in _type.lower():
|
||||||
|
if key == "dict_":
|
||||||
|
field.field_type = "file"
|
||||||
|
field.suffixes = [".json", ".yaml", ".yml"]
|
||||||
|
field.file_types = ["json", "yaml", "yml"]
|
||||||
|
else:
|
||||||
|
field.field_type = "code"
|
||||||
|
|
@ -1,10 +1,56 @@
|
||||||
|
import json
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
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.frontend_node.constants import CTRANSFORMERS_DEFAULT_CONFIG
|
||||||
|
from langflow.template.frontend_node.constants import OPENAI_API_BASE_INFO
|
||||||
|
|
||||||
|
|
||||||
class LLMFrontendNode(FrontendNode):
|
class LLMFrontendNode(FrontendNode):
|
||||||
|
def add_extra_fields(self) -> None:
|
||||||
|
if "VertexAI" in self.template.type_name:
|
||||||
|
# Add credentials field which should of type file.
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="file",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="credentials",
|
||||||
|
value="",
|
||||||
|
suffixes=[".json"],
|
||||||
|
fileTypes=["json"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_vertex_field(field: TemplateField, name: str):
|
||||||
|
if "VertexAI" in name:
|
||||||
|
advanced_fields = [
|
||||||
|
"tuned_model_name",
|
||||||
|
"verbose",
|
||||||
|
"top_p",
|
||||||
|
"top_k",
|
||||||
|
"max_output_tokens",
|
||||||
|
]
|
||||||
|
if field.name in advanced_fields:
|
||||||
|
field.advanced = True
|
||||||
|
show_fields = [
|
||||||
|
"tuned_model_name",
|
||||||
|
"verbose",
|
||||||
|
"project",
|
||||||
|
"location",
|
||||||
|
"credentials",
|
||||||
|
"max_output_tokens",
|
||||||
|
"model_name",
|
||||||
|
"temperature",
|
||||||
|
"top_p",
|
||||||
|
"top_k",
|
||||||
|
]
|
||||||
|
|
||||||
|
if field.name in show_fields:
|
||||||
|
field.show = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_openai_field(field: TemplateField):
|
def format_openai_field(field: TemplateField):
|
||||||
if "openai" in field.name.lower():
|
if "openai" in field.name.lower():
|
||||||
|
|
@ -15,6 +61,13 @@ class LLMFrontendNode(FrontendNode):
|
||||||
if "key" not in field.name.lower() and "token" not in field.name.lower():
|
if "key" not in field.name.lower() and "token" not in field.name.lower():
|
||||||
field.password = False
|
field.password = False
|
||||||
|
|
||||||
|
if field.name == "openai_api_base":
|
||||||
|
field.info = OPENAI_API_BASE_INFO
|
||||||
|
|
||||||
|
def add_extra_base_classes(self) -> None:
|
||||||
|
if "BaseLLM" not in self.base_classes:
|
||||||
|
self.base_classes.append("BaseLLM")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_azure_field(field: TemplateField):
|
def format_azure_field(field: TemplateField):
|
||||||
if field.name == "model_name":
|
if field.name == "model_name":
|
||||||
|
|
@ -31,6 +84,13 @@ class LLMFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = not field.required
|
field.advanced = not field.required
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_ctransformers_field(field: TemplateField):
|
||||||
|
if field.name == "config":
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
field.value = json.dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent=2)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
display_names_dict = {
|
display_names_dict = {
|
||||||
|
|
@ -38,10 +98,13 @@ class LLMFrontendNode(FrontendNode):
|
||||||
}
|
}
|
||||||
FrontendNode.format_field(field, name)
|
FrontendNode.format_field(field, name)
|
||||||
LLMFrontendNode.format_openai_field(field)
|
LLMFrontendNode.format_openai_field(field)
|
||||||
|
LLMFrontendNode.format_ctransformers_field(field)
|
||||||
if name and "azure" in name.lower():
|
if name and "azure" in name.lower():
|
||||||
LLMFrontendNode.format_azure_field(field)
|
LLMFrontendNode.format_azure_field(field)
|
||||||
if name and "llama" in name.lower():
|
if name and "llama" in name.lower():
|
||||||
LLMFrontendNode.format_llama_field(field)
|
LLMFrontendNode.format_llama_field(field)
|
||||||
|
if name and "vertex" in name.lower():
|
||||||
|
LLMFrontendNode.format_vertex_field(field, name)
|
||||||
SHOW_FIELDS = ["repo_id"]
|
SHOW_FIELDS = ["repo_id"]
|
||||||
if field.name in SHOW_FIELDS:
|
if field.name in SHOW_FIELDS:
|
||||||
field.show = True
|
field.show = True
|
||||||
|
|
@ -77,6 +140,17 @@ class LLMFrontendNode(FrontendNode):
|
||||||
"model_file",
|
"model_file",
|
||||||
"model_type",
|
"model_type",
|
||||||
"deployment_name",
|
"deployment_name",
|
||||||
|
"credentials",
|
||||||
]:
|
]:
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
field.show = True
|
field.show = True
|
||||||
|
if field.name == "credentials":
|
||||||
|
field.field_type = "file"
|
||||||
|
if name == "VertexAI" and field.name not in [
|
||||||
|
"callbacks",
|
||||||
|
"client",
|
||||||
|
"stop",
|
||||||
|
"tags",
|
||||||
|
"cache",
|
||||||
|
]:
|
||||||
|
field.show = True
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,24 @@ from typing import Optional
|
||||||
|
|
||||||
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.frontend_node.constants import INPUT_KEY_INFO, OUTPUT_KEY_INFO
|
||||||
|
from langflow.template.template.base import Template
|
||||||
|
from langchain.memory.chat_message_histories.postgres import DEFAULT_CONNECTION_STRING
|
||||||
|
from langchain.memory.chat_message_histories.mongodb import (
|
||||||
|
DEFAULT_COLLECTION_NAME,
|
||||||
|
DEFAULT_DBNAME,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MemoryFrontendNode(FrontendNode):
|
class MemoryFrontendNode(FrontendNode):
|
||||||
#! Needs testing
|
#! Needs testing
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
|
# chat history should have another way to add common field?
|
||||||
|
# prevent adding incorect field in ChatMessageHistory
|
||||||
|
base_message_classes = ["BaseEntityStore", "BaseChatMessageHistory"]
|
||||||
|
if any(base_class in self.base_classes for base_class in base_message_classes):
|
||||||
|
return
|
||||||
|
|
||||||
# add return_messages field
|
# add return_messages field
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
|
|
@ -18,6 +31,28 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
value=False,
|
value=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# add input_key and output_key str fields
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="input_key",
|
||||||
|
advanced=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if self.template.type_name not in {"VectorStoreRetrieverMemory"}:
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="output_key",
|
||||||
|
advanced=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
|
@ -36,3 +71,115 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
field.required = False
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
if field.name in {"input_key", "output_key"}:
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.value = ""
|
||||||
|
field.info = (
|
||||||
|
INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
if field.name == "memory_key":
|
||||||
|
field.value = "chat_history"
|
||||||
|
if field.name == "chat_memory":
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.required = False
|
||||||
|
if field.name == "url":
|
||||||
|
field.show = True
|
||||||
|
if field.name == "entity_store":
|
||||||
|
field.show = False
|
||||||
|
if name == "ConversationEntityMemory" and field.name == "memory_key":
|
||||||
|
field.show = False
|
||||||
|
field.required = False
|
||||||
|
|
||||||
|
|
||||||
|
class PostgresChatMessageHistoryFrontendNode(MemoryFrontendNode):
|
||||||
|
name: str = "PostgresChatMessageHistory"
|
||||||
|
template: Template = Template(
|
||||||
|
type_name="PostgresChatMessageHistory",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
name="session_id",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="connection_string",
|
||||||
|
value=DEFAULT_CONNECTION_STRING,
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value="message_store",
|
||||||
|
name="table_name",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "Memory store with Postgres"
|
||||||
|
base_classes: list[str] = ["PostgresChatMessageHistory", "BaseChatMessageHistory"]
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBChatMessageHistoryFrontendNode(MemoryFrontendNode):
|
||||||
|
name: str = "MongoDBChatMessageHistory"
|
||||||
|
template: Template = Template(
|
||||||
|
# langchain/memory/chat_message_histories/mongodb.py
|
||||||
|
# connection_string: str,
|
||||||
|
# session_id: str,
|
||||||
|
# database_name: str = DEFAULT_DBNAME,
|
||||||
|
# collection_name: str = DEFAULT_COLLECTION_NAME,
|
||||||
|
type_name="MongoDBChatMessageHistory",
|
||||||
|
fields=[
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
name="session_id",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="connection_string",
|
||||||
|
value="",
|
||||||
|
info="MongoDB connection string (e.g mongodb://mongo_user:password123@mongo:27017)",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value=DEFAULT_DBNAME,
|
||||||
|
name="database_name",
|
||||||
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value=DEFAULT_COLLECTION_NAME,
|
||||||
|
name="collection_name",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
description: str = "Memory store with MongoDB"
|
||||||
|
base_classes: list[str] = ["MongoDBChatMessageHistory", "BaseChatMessageHistory"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class OutputParserFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
field.show = True
|
||||||
15
src/backend/langflow/template/frontend_node/retrievers.py
Normal file
15
src/backend/langflow/template/frontend_node/retrievers.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieverFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
# Define common field attributes
|
||||||
|
field.show = True
|
||||||
|
if field.name == "parser_key":
|
||||||
|
field.display_name = "Parser Key"
|
||||||
|
field.password = False
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
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 langchain.text_splitter import Language
|
||||||
|
|
||||||
|
|
||||||
class TextSplittersFrontendNode(FrontendNode):
|
class TextSplittersFrontendNode(FrontendNode):
|
||||||
|
def add_extra_base_classes(self) -> None:
|
||||||
|
self.base_classes = ["Document"]
|
||||||
|
self.output_types = ["Document"]
|
||||||
|
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
field_type="BaseLoader",
|
field_type="Document",
|
||||||
required=True,
|
required=True,
|
||||||
show=True,
|
show=True,
|
||||||
name="documents",
|
name="documents",
|
||||||
|
|
@ -17,12 +22,30 @@ class TextSplittersFrontendNode(FrontendNode):
|
||||||
name = "separator"
|
name = "separator"
|
||||||
elif self.template.type_name == "RecursiveCharacterTextSplitter":
|
elif self.template.type_name == "RecursiveCharacterTextSplitter":
|
||||||
name = "separators"
|
name = "separators"
|
||||||
|
# Add a field for type of separator
|
||||||
|
# which will have Text or any value from the
|
||||||
|
# Language enum
|
||||||
|
options = [x.value for x in Language] + ["Text"]
|
||||||
|
options.sort()
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="separator_type",
|
||||||
|
advanced=False,
|
||||||
|
is_list=True,
|
||||||
|
options=options,
|
||||||
|
value="Text",
|
||||||
|
display_name="Separator Type",
|
||||||
|
)
|
||||||
|
)
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
field_type="str",
|
field_type="str",
|
||||||
required=True,
|
required=True,
|
||||||
show=True,
|
show=True,
|
||||||
value=".",
|
value="\\n",
|
||||||
name=name,
|
name=name,
|
||||||
display_name="Separator",
|
display_name="Separator",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ class ToolNode(FrontendNode):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "Converts a chain, agent or function into a tool."
|
description: str = "Converts a chain, agent or function into a tool."
|
||||||
base_classes: list[str] = ["Tool"]
|
base_classes: list[str] = ["Tool", "BaseTool"]
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return super().to_dict()
|
return super().to_dict()
|
||||||
|
|
@ -96,10 +96,20 @@ class PythonFunctionToolNode(FrontendNode):
|
||||||
name="code",
|
name="code",
|
||||||
advanced=False,
|
advanced=False,
|
||||||
),
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value=False,
|
||||||
|
name="return_direct",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "Python function to be executed."
|
description: str = "Python function to be executed."
|
||||||
base_classes: list[str] = ["Tool"]
|
base_classes: list[str] = ["BaseTool", "Tool"]
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return super().to_dict()
|
return super().to_dict()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -6,6 +6,19 @@ from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
class VectorStoreFrontendNode(FrontendNode):
|
class VectorStoreFrontendNode(FrontendNode):
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
|
extra_fields: List[TemplateField] = []
|
||||||
|
# Add search_kwargs field
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="search_kwargs",
|
||||||
|
field_type="code",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="{}",
|
||||||
|
)
|
||||||
|
extra_fields.append(extra_field)
|
||||||
if self.template.type_name == "Weaviate":
|
if self.template.type_name == "Weaviate":
|
||||||
extra_field = TemplateField(
|
extra_field = TemplateField(
|
||||||
name="weaviate_url",
|
name="weaviate_url",
|
||||||
|
|
@ -17,17 +30,203 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
multiline=False,
|
multiline=False,
|
||||||
value="http://localhost:8080",
|
value="http://localhost:8080",
|
||||||
)
|
)
|
||||||
|
# Add client_kwargs field
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="client_kwargs",
|
||||||
|
field_type="code",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="{}",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
|
||||||
self.template.add_field(extra_field)
|
elif self.template.type_name == "Chroma":
|
||||||
|
# New bool field for persist parameter
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="persist",
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
value=False,
|
||||||
|
display_name="Persist",
|
||||||
|
)
|
||||||
|
extra_fields.append(extra_field)
|
||||||
|
elif self.template.type_name == "Pinecone":
|
||||||
|
# add pinecone_api_key and pinecone_env
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="pinecone_api_key",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
password=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="pinecone_env",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
elif self.template.type_name == "FAISS":
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="folder_path",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Local Path",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="index_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
value="",
|
||||||
|
display_name="Index Name",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
elif self.template.type_name == "SupabaseVectorStore":
|
||||||
|
self.display_name = "Supabase"
|
||||||
|
# Add table_name and query_name
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="table_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="query_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
# Add supabase_url and supabase_service_key
|
||||||
|
extra_field3 = TemplateField(
|
||||||
|
name="supabase_url",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field4 = TemplateField(
|
||||||
|
name="supabase_service_key",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
password=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||||
|
|
||||||
|
elif self.template.type_name == "MongoDBAtlasVectorSearch":
|
||||||
|
self.display_name = "MongoDB Atlas"
|
||||||
|
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="mongodb_atlas_cluster_uri",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="MongoDB Atlas Cluster URI",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="collection_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Collection Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field3 = TemplateField(
|
||||||
|
name="db_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Database Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field4 = TemplateField(
|
||||||
|
name="index_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Index Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||||
|
|
||||||
|
if extra_fields:
|
||||||
|
for field in extra_fields:
|
||||||
|
self.template.add_field(field)
|
||||||
|
|
||||||
def add_extra_base_classes(self) -> None:
|
def add_extra_base_classes(self) -> None:
|
||||||
self.base_classes.append("BaseRetriever")
|
self.base_classes.extend(("BaseRetriever", "VectorStoreRetriever"))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
FrontendNode.format_field(field, name)
|
FrontendNode.format_field(field, name)
|
||||||
# Define common field attributes
|
# Define common field attributes
|
||||||
basic_fields = ["work_dir", "collection_name", "api_key", "location"]
|
basic_fields = [
|
||||||
|
"work_dir",
|
||||||
|
"collection_name",
|
||||||
|
"api_key",
|
||||||
|
"location",
|
||||||
|
"persist_directory",
|
||||||
|
"persist",
|
||||||
|
"weaviate_url",
|
||||||
|
"index_name",
|
||||||
|
"namespace",
|
||||||
|
"folder_path",
|
||||||
|
"table_name",
|
||||||
|
"query_name",
|
||||||
|
"supabase_url",
|
||||||
|
"supabase_service_key",
|
||||||
|
"mongodb_atlas_cluster_uri",
|
||||||
|
"collection_name",
|
||||||
|
"db_name",
|
||||||
|
]
|
||||||
advanced_fields = [
|
advanced_fields = [
|
||||||
"n_dim",
|
"n_dim",
|
||||||
"key",
|
"key",
|
||||||
|
|
@ -43,14 +242,21 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
"https",
|
"https",
|
||||||
"prefer_grpc",
|
"prefer_grpc",
|
||||||
"grpc_port",
|
"grpc_port",
|
||||||
|
"pinecone_api_key",
|
||||||
|
"pinecone_env",
|
||||||
|
"client_kwargs",
|
||||||
|
"search_kwargs",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check and set field attributes
|
# Check and set field attributes
|
||||||
if field.name == "texts":
|
if field.name == "texts":
|
||||||
|
# if field.name is "texts" it has to be replaced
|
||||||
|
# when instantiating the vectorstores
|
||||||
field.name = "documents"
|
field.name = "documents"
|
||||||
field.field_type = "TextSplitter"
|
|
||||||
field.display_name = "Text Splitter"
|
field.field_type = "Document"
|
||||||
field.required = True
|
field.display_name = "Documents"
|
||||||
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
|
|
@ -78,5 +284,6 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
if "key" in field.name:
|
if "key" in field.name:
|
||||||
field.password = False
|
field.password = False
|
||||||
# TODO: Weaviate requires weaviate_url to be passed as it is not part of
|
|
||||||
# the class or from_texts method. We need the add_extra_fields to fix this
|
elif field.name == "text_key":
|
||||||
|
field.show = False
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Callable, Optional, Union
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
|
|
||||||
|
|
||||||
class Template(BaseModel):
|
class Template(BaseModel):
|
||||||
|
|
@ -18,8 +19,15 @@ class Template(BaseModel):
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
format_field_func(field, name)
|
format_field_func(field, name)
|
||||||
|
|
||||||
|
def sort_fields(self):
|
||||||
|
# first sort alphabetically
|
||||||
|
# then sort fields so that fields that have .field_type in DIRECT_TYPES are first
|
||||||
|
self.fields.sort(key=lambda x: x.name)
|
||||||
|
self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False)
|
||||||
|
|
||||||
def to_dict(self, format_field_func=None):
|
def to_dict(self, format_field_func=None):
|
||||||
self.process_fields(self.type_name, format_field_func)
|
self.process_fields(self.type_name, format_field_func)
|
||||||
|
self.sort_fields()
|
||||||
result = {field.name: field.to_dict() for field in self.fields}
|
result = {field.name: field.to_dict() for field in self.fields}
|
||||||
result["_type"] = self.type_name # type: ignore
|
result["_type"] = self.type_name # type: ignore
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,4 @@ def python_function(text: str) -> str:
|
||||||
\"\"\"This is a default python function that returns the input text\"\"\"
|
\"\"\"This is a default python function that returns the input text\"\"\"
|
||||||
return text
|
return text
|
||||||
"""
|
"""
|
||||||
|
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ def build_template_from_method(
|
||||||
"required": param.default == param.empty,
|
"required": param.default == param.empty,
|
||||||
}
|
}
|
||||||
for name, param in params.items()
|
for name, param in params.items()
|
||||||
|
if name not in ["self", "kwargs", "args"]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,13 +234,20 @@ def format_dict(d, name: Optional[str] = None):
|
||||||
|
|
||||||
_type = value["type"]
|
_type = value["type"]
|
||||||
|
|
||||||
|
if not isinstance(_type, str):
|
||||||
|
_type = _type.__name__
|
||||||
|
|
||||||
# Remove 'Optional' wrapper
|
# Remove 'Optional' wrapper
|
||||||
if "Optional" in _type:
|
if "Optional" in _type:
|
||||||
_type = _type.replace("Optional[", "")[:-1]
|
_type = _type.replace("Optional[", "")[:-1]
|
||||||
|
|
||||||
# Check for list type
|
# Check for list type
|
||||||
if "List" in _type or "Sequence" in _type or "Set" in _type:
|
if "List" in _type or "Sequence" in _type or "Set" in _type:
|
||||||
_type = _type.replace("List[", "")[:-1]
|
_type = (
|
||||||
|
_type.replace("List[", "")
|
||||||
|
.replace("Sequence[", "")
|
||||||
|
.replace("Set[", "")[:-1]
|
||||||
|
)
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
else:
|
else:
|
||||||
value["list"] = False
|
value["list"] = False
|
||||||
|
|
@ -299,12 +307,15 @@ def format_dict(d, name: Optional[str] = None):
|
||||||
if name == "OpenAI" and key == "model_name":
|
if name == "OpenAI" and key == "model_name":
|
||||||
value["options"] = constants.OPENAI_MODELS
|
value["options"] = constants.OPENAI_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.OPENAI_MODELS[0]
|
||||||
elif name == "ChatOpenAI" and key == "model_name":
|
elif name == "ChatOpenAI" and key == "model_name":
|
||||||
value["options"] = constants.CHAT_OPENAI_MODELS
|
value["options"] = constants.CHAT_OPENAI_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.CHAT_OPENAI_MODELS[0]
|
||||||
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
||||||
value["options"] = constants.ANTHROPIC_MODELS
|
value["options"] = constants.ANTHROPIC_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.ANTHROPIC_MODELS[0]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<title>LangFlow</title>
|
<script src="/node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script>
|
||||||
|
<title>Langflow</title>
|
||||||
</head>
|
</head>
|
||||||
<body id='body' style="width: 100%; height:100%">
|
<body id='body' style="width: 100%; height:100%">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|
|
||||||
4103
src/frontend/package-lock.json
generated
4103
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,19 +8,23 @@
|
||||||
"@headlessui/react": "^1.7.10",
|
"@headlessui/react": "^1.7.10",
|
||||||
"@heroicons/react": "^2.0.15",
|
"@heroicons/react": "^2.0.15",
|
||||||
"@mui/material": "^5.11.9",
|
"@mui/material": "^5.11.9",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
"@radix-ui/react-menubar": "^1.0.3",
|
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-menubar": "^1.0.3",
|
||||||
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-tooltip": "^1.0.6",
|
||||||
"@tabler/icons-react": "^2.18.0",
|
"@tabler/icons-react": "^2.18.0",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
"@tailwindcss/line-clamp": "^0.4.4",
|
||||||
|
"accordion": "^3.0.2",
|
||||||
"ace-builds": "^1.16.0",
|
"ace-builds": "^1.16.0",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
|
|
@ -28,6 +32,7 @@
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"class-variance-authority": "^0.6.0",
|
"class-variance-authority": "^0.6.0",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
"dompurify": "^3.0.4",
|
||||||
"esbuild": "^0.17.18",
|
"esbuild": "^0.17.18",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.233.0",
|
"lucide-react": "^0.233.0",
|
||||||
|
|
@ -47,7 +52,7 @@
|
||||||
"rehype-mathjax": "^4.0.2",
|
"rehype-mathjax": "^4.0.2",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"remark-math": "^5.1.1",
|
"remark-math": "^5.1.1",
|
||||||
"shadcn-ui": "^0.1.3",
|
"shadcn-ui": "^0.2.2",
|
||||||
"short-unique-id": "^4.4.4",
|
"short-unique-id": "^4.4.4",
|
||||||
"switch": "^0.0.0",
|
"switch": "^0.0.0",
|
||||||
"table": "^6.8.1",
|
"table": "^6.8.1",
|
||||||
|
|
@ -98,7 +103,11 @@
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"daisyui": "^3.1.1",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
|
"prettier-plugin-organize-imports": "^3.2.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.3.9"
|
"vite": "^4.3.9"
|
||||||
|
|
|
||||||
3
src/frontend/prettier.config.js
Normal file
3
src/frontend/prettier.config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: [require("prettier-plugin-tailwindcss")],
|
||||||
|
};
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
import "reactflow/dist/style.css";
|
|
||||||
import { useState, useEffect, useContext } from "react";
|
|
||||||
import "./App.css";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import "reactflow/dist/style.css";
|
||||||
|
import "./App.css";
|
||||||
|
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
import ErrorAlert from "./alerts/error";
|
import ErrorAlert from "./alerts/error";
|
||||||
import NoticeAlert from "./alerts/notice";
|
import NoticeAlert from "./alerts/notice";
|
||||||
import SuccessAlert from "./alerts/success";
|
import SuccessAlert from "./alerts/success";
|
||||||
|
import CrashErrorComponent from "./components/CrashErrorComponent";
|
||||||
|
import Header from "./components/headerComponent";
|
||||||
import { alertContext } from "./contexts/alertContext";
|
import { alertContext } from "./contexts/alertContext";
|
||||||
import { locationContext } from "./contexts/locationContext";
|
import { locationContext } from "./contexts/locationContext";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
|
||||||
import CrashErrorComponent from "./components/CrashErrorComponent";
|
|
||||||
import { TabsContext } from "./contexts/tabsContext";
|
import { TabsContext } from "./contexts/tabsContext";
|
||||||
import { getVersion } from "./controllers/API";
|
|
||||||
import Router from "./routes";
|
import Router from "./routes";
|
||||||
import Header from "./components/headerComponent";
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
let { setCurrent, setShowSideBar, setIsStackedOpen } =
|
let { setCurrent, setShowSideBar, setIsStackedOpen } =
|
||||||
|
|
@ -51,6 +50,13 @@ export default function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If there is an error alert open with data, add it to the alertsList
|
// If there is an error alert open with data, add it to the alertsList
|
||||||
if (errorOpen && errorData) {
|
if (errorOpen && errorData) {
|
||||||
|
if (
|
||||||
|
alertsList.length > 0 &&
|
||||||
|
JSON.stringify(alertsList[alertsList.length - 1].data) ===
|
||||||
|
JSON.stringify(errorData)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setErrorOpen(false);
|
setErrorOpen(false);
|
||||||
setAlertsList((old) => {
|
setAlertsList((old) => {
|
||||||
let newAlertsList = [
|
let newAlertsList = [
|
||||||
|
|
@ -62,6 +68,13 @@ export default function App() {
|
||||||
}
|
}
|
||||||
// If there is a notice alert open with data, add it to the alertsList
|
// If there is a notice alert open with data, add it to the alertsList
|
||||||
else if (noticeOpen && noticeData) {
|
else if (noticeOpen && noticeData) {
|
||||||
|
if (
|
||||||
|
alertsList.length > 0 &&
|
||||||
|
JSON.stringify(alertsList[alertsList.length - 1].data) ===
|
||||||
|
JSON.stringify(noticeData)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setNoticeOpen(false);
|
setNoticeOpen(false);
|
||||||
setAlertsList((old) => {
|
setAlertsList((old) => {
|
||||||
let newAlertsList = [
|
let newAlertsList = [
|
||||||
|
|
@ -73,6 +86,13 @@ export default function App() {
|
||||||
}
|
}
|
||||||
// If there is a success alert open with data, add it to the alertsList
|
// If there is a success alert open with data, add it to the alertsList
|
||||||
else if (successOpen && successData) {
|
else if (successOpen && successData) {
|
||||||
|
if (
|
||||||
|
alertsList.length > 0 &&
|
||||||
|
JSON.stringify(alertsList[alertsList.length - 1].data) ===
|
||||||
|
JSON.stringify(successData)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setSuccessOpen(false);
|
setSuccessOpen(false);
|
||||||
setAlertsList((old) => {
|
setAlertsList((old) => {
|
||||||
let newAlertsList = [
|
let newAlertsList = [
|
||||||
|
|
@ -103,7 +123,7 @@ export default function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//need parent component with width and height
|
//need parent component with width and height
|
||||||
<div className="h-full flex flex-col">
|
<div className="flex h-full flex-col">
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
onReset={() => {
|
onReset={() => {
|
||||||
window.localStorage.removeItem("tabsData");
|
window.localStorage.removeItem("tabsData");
|
||||||
|
|
@ -117,10 +137,7 @@ export default function App() {
|
||||||
<Router />
|
<Router />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div></div>
|
<div></div>
|
||||||
<div
|
<div className="app-div" style={{ zIndex: 999 }}>
|
||||||
className="flex flex-col-reverse fixed bottom-5 left-5"
|
|
||||||
style={{ zIndex: 999 }}
|
|
||||||
>
|
|
||||||
{alertsList.map((alert) => (
|
{alertsList.map((alert) => (
|
||||||
<div key={alert.id}>
|
<div key={alert.id}>
|
||||||
{alert.type === "error" ? (
|
{alert.type === "error" ? (
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,32 @@
|
||||||
|
import { Info } from "lucide-react";
|
||||||
|
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||||
import {
|
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||||
classNames,
|
|
||||||
groupByFamily,
|
|
||||||
isValidConnection,
|
|
||||||
} from "../../../../utils";
|
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import InputComponent from "../../../../components/inputComponent";
|
|
||||||
import InputListComponent from "../../../../components/inputListComponent";
|
|
||||||
import TextAreaComponent from "../../../../components/textAreaComponent";
|
|
||||||
import { typesContext } from "../../../../contexts/typesContext";
|
|
||||||
import { ParameterComponentType } from "../../../../types/components";
|
|
||||||
import FloatComponent from "../../../../components/floatComponent";
|
|
||||||
import Dropdown from "../../../../components/dropdownComponent";
|
|
||||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||||
|
import Dropdown from "../../../../components/dropdownComponent";
|
||||||
|
import FloatComponent from "../../../../components/floatComponent";
|
||||||
|
import InputComponent from "../../../../components/inputComponent";
|
||||||
import InputFileComponent from "../../../../components/inputFileComponent";
|
import InputFileComponent from "../../../../components/inputFileComponent";
|
||||||
import { TabsContext } from "../../../../contexts/tabsContext";
|
import InputListComponent from "../../../../components/inputListComponent";
|
||||||
import IntComponent from "../../../../components/intComponent";
|
import IntComponent from "../../../../components/intComponent";
|
||||||
import PromptAreaComponent from "../../../../components/promptComponent";
|
import PromptAreaComponent from "../../../../components/promptComponent";
|
||||||
import { nodeNames, nodeIcons } from "../../../../utils";
|
import TextAreaComponent from "../../../../components/textAreaComponent";
|
||||||
import React from "react";
|
|
||||||
import { nodeColors } from "../../../../utils";
|
|
||||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
|
||||||
import { PopUpContext } from "../../../../contexts/popUpContext";
|
|
||||||
import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
||||||
|
import { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants";
|
||||||
|
import { PopUpContext } from "../../../../contexts/popUpContext";
|
||||||
|
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||||
|
import { typesContext } from "../../../../contexts/typesContext";
|
||||||
|
import { ParameterComponentType } from "../../../../types/components";
|
||||||
|
import { cleanEdges } from "../../../../util/reactflowUtils";
|
||||||
|
import {
|
||||||
|
classNames,
|
||||||
|
getRandomKeyByssmm,
|
||||||
|
groupByFamily,
|
||||||
|
isValidConnection,
|
||||||
|
nodeColors,
|
||||||
|
nodeIconsLucide,
|
||||||
|
nodeNames,
|
||||||
|
} from "../../../../utils";
|
||||||
|
|
||||||
export default function ParameterComponent({
|
export default function ParameterComponent({
|
||||||
left,
|
left,
|
||||||
|
|
@ -34,13 +38,17 @@ export default function ParameterComponent({
|
||||||
type,
|
type,
|
||||||
name = "",
|
name = "",
|
||||||
required = false,
|
required = false,
|
||||||
|
optionalHandle = null,
|
||||||
|
info = "",
|
||||||
}: ParameterComponentType) {
|
}: ParameterComponentType) {
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const refHtml = useRef(null);
|
const refHtml = useRef(null);
|
||||||
|
const refNumberComponents = useRef(0);
|
||||||
|
const infoHtml = useRef(null);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const [position, setPosition] = useState(0);
|
const [position, setPosition] = useState(0);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const { setTabsState, tabId } = useContext(TabsContext);
|
const { setTabsState, tabId, save } = useContext(TabsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
||||||
|
|
@ -53,10 +61,6 @@ export default function ParameterComponent({
|
||||||
updateNodeInternals(data.id);
|
updateNodeInternals(data.id);
|
||||||
}, [data.id, position, updateNodeInternals]);
|
}, [data.id, position, updateNodeInternals]);
|
||||||
|
|
||||||
const [enabled, setEnabled] = useState(
|
|
||||||
data.node.template[name]?.value ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {}, [closePopUp, data.node.template]);
|
useEffect(() => {}, [closePopUp, data.node.template]);
|
||||||
|
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
|
|
@ -71,6 +75,7 @@ export default function ParameterComponent({
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[tabId]: {
|
[tabId]: {
|
||||||
|
...prev[tabId],
|
||||||
isPending: true,
|
isPending: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -78,55 +83,91 @@ export default function ParameterComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const groupedObj = groupByFamily(myData, tooltipTitle);
|
infoHtml.current = (
|
||||||
|
<div className="h-full w-full break-words">
|
||||||
|
{info.split("\n").map((line, i) => (
|
||||||
|
<p key={i} className="block">
|
||||||
|
{line}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [info]);
|
||||||
|
|
||||||
refHtml.current = groupedObj.map((item, i) => (
|
useEffect(() => {
|
||||||
<span
|
const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
|
||||||
key={i}
|
|
||||||
className={classNames(
|
refNumberComponents.current = groupedObj[0]?.type?.length;
|
||||||
i > 0 ? "items-center flex mt-3" : "items-center flex"
|
|
||||||
)}
|
refHtml.current = groupedObj.map((item, i) => {
|
||||||
>
|
const Icon: any = nodeIconsLucide[item.family];
|
||||||
<div
|
|
||||||
className="h-5 w-5"
|
return (
|
||||||
style={{
|
<span
|
||||||
color: nodeColors[item.family],
|
key={getRandomKeyByssmm() + item.family + i}
|
||||||
}}
|
className={classNames(
|
||||||
|
i > 0 ? "mt-2 flex items-center" : "flex items-center"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{React.createElement(nodeIcons[item.family])}
|
<div
|
||||||
</div>
|
className="h-5 w-5"
|
||||||
<span className="ps-2 text-gray-950">
|
style={{
|
||||||
{nodeNames[item.family] ?? ""}{" "}
|
color: nodeColors[item.family],
|
||||||
<span className={classNames(left ? "hidden" : "")}>
|
}}
|
||||||
{" "}
|
>
|
||||||
-
|
<Icon
|
||||||
{item.type.split(", ").length > 2
|
className="h-5 w-5"
|
||||||
? item.type.split(", ").map((el, i) => (
|
strokeWidth={1.5}
|
||||||
<>
|
style={{
|
||||||
<span key={i}>
|
color: nodeColors[item.family] ?? nodeColors.unknown,
|
||||||
{i == item.type.split(", ").length - 1
|
}}
|
||||||
? el
|
/>
|
||||||
: (el += `, `)}
|
</div>
|
||||||
</span>
|
<span className="ps-2 text-xs text-foreground">
|
||||||
{i % 2 == 0 && i > 0 && <br></br>}
|
{nodeNames[item.family] ?? ""}{" "}
|
||||||
</>
|
<span className="text-xs">
|
||||||
))
|
{" "}
|
||||||
: item.type}
|
{item.type === "" ? "" : " - "}
|
||||||
|
{item.type.split(", ").length > 2
|
||||||
|
? item.type.split(", ").map((el, i) => (
|
||||||
|
<React.Fragment key={el + i}>
|
||||||
|
<span>
|
||||||
|
{i === item.type.split(", ").length - 1
|
||||||
|
? el
|
||||||
|
: (el += `, `)}
|
||||||
|
</span>
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
: item.type}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
);
|
||||||
));
|
});
|
||||||
}, [tooltipTitle]);
|
}, [tooltipTitle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="w-full flex flex-wrap justify-between items-center bg-muted dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
|
className="mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<div className={"text-sm truncate w-full " + (left ? "" : "text-end")}>
|
<div
|
||||||
|
className={
|
||||||
|
"w-full truncate text-sm" +
|
||||||
|
(left ? "" : " text-end") +
|
||||||
|
(info !== "" ? " flex items-center" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
<span className="text-red-600">{required ? " *" : ""}</span>
|
<span className="text-status-red">{required ? " *" : ""}</span>
|
||||||
|
<div className="">
|
||||||
|
{info !== "" && (
|
||||||
|
<ShadTooltip content={infoHtml.current}>
|
||||||
|
<Info className="relative bottom-0.5 ml-2 h-3 w-3" />
|
||||||
|
</ShadTooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{left &&
|
{left &&
|
||||||
(type === "str" ||
|
(type === "str" ||
|
||||||
|
|
@ -135,14 +176,19 @@ export default function ParameterComponent({
|
||||||
type === "code" ||
|
type === "code" ||
|
||||||
type === "prompt" ||
|
type === "prompt" ||
|
||||||
type === "file" ||
|
type === "file" ||
|
||||||
type === "int") ? (
|
type === "int") &&
|
||||||
|
!optionalHandle ? (
|
||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
|
styleClasses={
|
||||||
|
refNumberComponents.current > MAX_LENGTH_TO_SCROLL_TOOLTIP
|
||||||
|
? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel"
|
||||||
|
: "tooltip-fixed-width"
|
||||||
|
}
|
||||||
delayDuration={0}
|
delayDuration={0}
|
||||||
content={refHtml.current}
|
content={refHtml.current}
|
||||||
side={left ? "left" : "right"}
|
side={left ? "left" : "right"}
|
||||||
open={refHtml?.current?.length > 0}
|
|
||||||
>
|
>
|
||||||
<Handle
|
<Handle
|
||||||
type={left ? "target" : "source"}
|
type={left ? "target" : "source"}
|
||||||
|
|
@ -153,7 +199,7 @@ export default function ParameterComponent({
|
||||||
}
|
}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
left ? "-ml-0.5 " : "-mr-0.5 ",
|
left ? "-ml-0.5 " : "-mr-0.5 ",
|
||||||
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
|
"h-3 w-3 rounded-full border-2 bg-background"
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
|
|
@ -195,13 +241,12 @@ export default function ParameterComponent({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : left === true && type === "bool" ? (
|
) : left === true && type === "bool" ? (
|
||||||
<div className="mt-2">
|
<div className="mt-2 w-full">
|
||||||
<ToggleShadComponent
|
<ToggleShadComponent
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
enabled={enabled}
|
enabled={data.node.template[name].value}
|
||||||
setEnabled={(t) => {
|
setEnabled={(t) => {
|
||||||
handleOnNewValue(t);
|
handleOnNewValue(t);
|
||||||
setEnabled(t);
|
|
||||||
}}
|
}}
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
|
|
@ -218,7 +263,7 @@ export default function ParameterComponent({
|
||||||
) : left === true &&
|
) : left === true &&
|
||||||
type === "str" &&
|
type === "str" &&
|
||||||
data.node.template[name].options ? (
|
data.node.template[name].options ? (
|
||||||
<div className="w-full">
|
<div className="mt-2 w-full">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={data.node.template[name].options}
|
options={data.node.template[name].options}
|
||||||
onSelect={handleOnNewValue}
|
onSelect={handleOnNewValue}
|
||||||
|
|
@ -226,22 +271,31 @@ export default function ParameterComponent({
|
||||||
></Dropdown>
|
></Dropdown>
|
||||||
</div>
|
</div>
|
||||||
) : left === true && type === "code" ? (
|
) : left === true && type === "code" ? (
|
||||||
<CodeAreaComponent
|
<div className="mt-2 w-full">
|
||||||
disabled={disabled}
|
<CodeAreaComponent
|
||||||
value={data.node.template[name].value ?? ""}
|
setNodeClass={(nodeClass) => {
|
||||||
onChange={handleOnNewValue}
|
data.node = nodeClass;
|
||||||
/>
|
}}
|
||||||
|
nodeClass={data.node}
|
||||||
|
disabled={disabled}
|
||||||
|
value={data.node.template[name].value ?? ""}
|
||||||
|
onChange={handleOnNewValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : left === true && type === "file" ? (
|
) : left === true && type === "file" ? (
|
||||||
<InputFileComponent
|
<div className="mt-2 w-full">
|
||||||
disabled={disabled}
|
<InputFileComponent
|
||||||
value={data.node.template[name].value ?? ""}
|
disabled={disabled}
|
||||||
onChange={handleOnNewValue}
|
value={data.node.template[name].value ?? ""}
|
||||||
fileTypes={data.node.template[name].fileTypes}
|
onChange={handleOnNewValue}
|
||||||
suffixes={data.node.template[name].suffixes}
|
fileTypes={data.node.template[name].fileTypes}
|
||||||
onFileChange={(t: string) => {
|
suffixes={data.node.template[name].suffixes}
|
||||||
data.node.template[name].content = t;
|
onFileChange={(t: string) => {
|
||||||
}}
|
data.node.template[name].file_path = t;
|
||||||
></InputFileComponent>
|
save();
|
||||||
|
}}
|
||||||
|
></InputFileComponent>
|
||||||
|
</div>
|
||||||
) : left === true && type === "int" ? (
|
) : left === true && type === "int" ? (
|
||||||
<div className="mt-2 w-full">
|
<div className="mt-2 w-full">
|
||||||
<IntComponent
|
<IntComponent
|
||||||
|
|
@ -252,11 +306,27 @@ export default function ParameterComponent({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : left === true && type === "prompt" ? (
|
) : left === true && type === "prompt" ? (
|
||||||
<PromptAreaComponent
|
<div className="mt-2 w-full">
|
||||||
disabled={disabled}
|
<PromptAreaComponent
|
||||||
value={data.node.template[name].value ?? ""}
|
field_name={name}
|
||||||
onChange={handleOnNewValue}
|
setNodeClass={(nodeClass) => {
|
||||||
/>
|
data.node = nodeClass;
|
||||||
|
if (reactFlowInstance) {
|
||||||
|
cleanEdges({
|
||||||
|
flow: {
|
||||||
|
edges: reactFlowInstance.getEdges(),
|
||||||
|
nodes: reactFlowInstance.getNodes(),
|
||||||
|
},
|
||||||
|
updateEdge: (edge) => reactFlowInstance.setEdges(edge),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
nodeClass={data.node}
|
||||||
|
disabled={disabled}
|
||||||
|
value={data.node.template[name].value ?? ""}
|
||||||
|
onChange={handleOnNewValue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
import { classNames, nodeColors, nodeIcons, toTitleCase } from "../../utils";
|
import { Zap } from "lucide-react";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { NodeToolbar } from "reactflow";
|
||||||
import { useContext, useState, useEffect, useRef } from "react";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import Tooltip from "../../components/TooltipComponent";
|
||||||
|
import { useSSE } from "../../contexts/SSEContext";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import NodeModal from "../../modals/NodeModal";
|
import NodeModal from "../../modals/NodeModal";
|
||||||
import Tooltip from "../../components/TooltipComponent";
|
|
||||||
import { NodeToolbar } from "reactflow";
|
|
||||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||||
|
import { NodeDataType } from "../../types/flow";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import {
|
||||||
import { useSSE } from "../../contexts/SSEContext";
|
classNames,
|
||||||
|
nodeColors,
|
||||||
|
nodeIconsLucide,
|
||||||
|
toTitleCase,
|
||||||
|
} from "../../utils";
|
||||||
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
|
|
@ -25,11 +30,13 @@ export default function GenericNode({
|
||||||
const { types, deleteNode } = useContext(typesContext);
|
const { types, deleteNode } = useContext(typesContext);
|
||||||
|
|
||||||
const { closePopUp, openPopUp } = useContext(PopUpContext);
|
const { closePopUp, openPopUp } = useContext(PopUpContext);
|
||||||
|
// any to avoid type conflict
|
||||||
const Icon = nodeIcons[data.type] || nodeIcons[types[data.type]];
|
const Icon: any =
|
||||||
|
nodeIconsLucide[data.type] || nodeIconsLucide[types[data.type]];
|
||||||
const [validationStatus, setValidationStatus] = useState(null);
|
const [validationStatus, setValidationStatus] = useState(null);
|
||||||
// State for outline color
|
// State for outline color
|
||||||
const { sseData, isBuilding } = useSSE();
|
const { sseData, isBuilding } = useSSE();
|
||||||
|
const refHtml = useRef(null);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// if (reactFlowInstance) {
|
// if (reactFlowInstance) {
|
||||||
|
|
@ -60,9 +67,7 @@ export default function GenericNode({
|
||||||
deleteNode(data.id);
|
deleteNode(data.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {}, [closePopUp, data.node.template]);
|
useEffect(() => {}, [closePopUp, data.node.template]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NodeToolbar>
|
<NodeToolbar>
|
||||||
|
|
@ -75,25 +80,28 @@ export default function GenericNode({
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
selected ? "border border-ring" : "border dark:border-gray-700",
|
selected ? "border border-ring" : "border",
|
||||||
"prompt-node relative flex w-96 flex-col justify-center rounded-lg bg-white dark:bg-gray-900"
|
"generic-node-div"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-between gap-8 rounded-t-lg border-b bg-muted p-4 dark:border-b-gray-700 dark:bg-gray-800 dark:text-white ">
|
<div className="generic-node-div-title">
|
||||||
<div className="flex w-full items-center gap-2 truncate text-lg">
|
<div className="generic-node-title-arrangement">
|
||||||
<Icon
|
<Icon
|
||||||
className="h-10 w-10 rounded p-1"
|
strokeWidth={1.5}
|
||||||
|
className="generic-node-icon"
|
||||||
style={{
|
style={{
|
||||||
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="ml-2 truncate">
|
<div className="generic-node-tooltip-div">
|
||||||
<ShadTooltip delayDuration={1500} content={data.type}>
|
<ShadTooltip content={data.node.display_name}>
|
||||||
<div className="ml-2 truncate text-gray-800">{data.type}</div>
|
<div className="generic-node-tooltip-div text-primary">
|
||||||
|
{data.node.display_name}
|
||||||
|
</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="round-button-div">
|
||||||
<button
|
<button
|
||||||
className="relative"
|
className="relative"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
|
|
@ -102,45 +110,55 @@ export default function GenericNode({
|
||||||
}}
|
}}
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="round-button-div">
|
||||||
<div>
|
<div>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
!validationStatus ? (
|
isBuilding ? (
|
||||||
"Validating..."
|
<span>Building...</span>
|
||||||
|
) : !validationStatus ? (
|
||||||
|
<span className="flex">
|
||||||
|
Build{" "}
|
||||||
|
<Zap
|
||||||
|
className="mx-0.5 h-5 fill-build-trigger stroke-build-trigger stroke-1"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>{" "}
|
||||||
|
flow to validate status.
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="max-h-96 overflow-auto">
|
<div className="max-h-96 overflow-auto">
|
||||||
{validationStatus.params ||
|
{validationStatus.params
|
||||||
""
|
? validationStatus.params
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line, index) => <div key={index}>{line}</div>)}
|
.map((line, index) => <div key={index}>{line}</div>)
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-5 h-5 relative top-[3px]">
|
<div className="generic-node-status-position">
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
validationStatus && validationStatus.valid
|
validationStatus && validationStatus.valid
|
||||||
? "w-4 h-4 rounded-full bg-green-500 opacity-100"
|
? "green-status"
|
||||||
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
|
: "status-build-animation",
|
||||||
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
|
"status-div"
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
validationStatus && !validationStatus.valid
|
validationStatus && !validationStatus.valid
|
||||||
? "w-4 h-4 rounded-full bg-red-500 opacity-100"
|
? "red-status"
|
||||||
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
|
: "status-build-animation",
|
||||||
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
|
"status-div"
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
!validationStatus || isBuilding
|
!validationStatus || isBuilding
|
||||||
? "w-4 h-4 rounded-full bg-yellow-500 opacity-100"
|
? "yellow-status"
|
||||||
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
|
: "status-build-animation",
|
||||||
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
|
"status-div"
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -149,10 +167,8 @@ export default function GenericNode({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-full w-full py-5 text-gray-800">
|
<div className="generic-node-desc">
|
||||||
<div className="w-full px-5 pb-3 text-sm text-muted-foreground">
|
<div className="generic-node-desc-text">{data.node.description}</div>
|
||||||
{data.node.description}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{Object.keys(data.node.template)
|
{Object.keys(data.node.template)
|
||||||
|
|
@ -162,7 +178,7 @@ export default function GenericNode({
|
||||||
{/* {idx === 0 ? (
|
{/* {idx === 0 ? (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"px-5 py-2 mt-2 dark:text-white text-center",
|
"px-5 py-2 mt-2 text-center",
|
||||||
Object.keys(data.node.template).filter(
|
Object.keys(data.node.template).filter(
|
||||||
(key) =>
|
(key) =>
|
||||||
!key.startsWith("_") &&
|
!key.startsWith("_") &&
|
||||||
|
|
@ -184,6 +200,7 @@ export default function GenericNode({
|
||||||
data={data}
|
data={data}
|
||||||
color={
|
color={
|
||||||
nodeColors[types[data.node.template[t].type]] ??
|
nodeColors[types[data.node.template[t].type]] ??
|
||||||
|
nodeColors[data.node.template[t].type] ??
|
||||||
nodeColors.unknown
|
nodeColors.unknown
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
|
|
@ -193,12 +210,24 @@ export default function GenericNode({
|
||||||
? toTitleCase(data.node.template[t].name)
|
? toTitleCase(data.node.template[t].name)
|
||||||
: toTitleCase(t)
|
: toTitleCase(t)
|
||||||
}
|
}
|
||||||
|
info={data.node.template[t].info}
|
||||||
name={t}
|
name={t}
|
||||||
tooltipTitle={data.node.template[t].type}
|
tooltipTitle={
|
||||||
|
data.node.template[t].input_types?.join("\n") ??
|
||||||
|
data.node.template[t].type
|
||||||
|
}
|
||||||
required={data.node.template[t].required}
|
required={data.node.template[t].required}
|
||||||
id={data.node.template[t].type + "|" + t + "|" + data.id}
|
id={
|
||||||
|
(data.node.template[t].input_types?.join(";") ??
|
||||||
|
data.node.template[t].type) +
|
||||||
|
"|" +
|
||||||
|
t +
|
||||||
|
"|" +
|
||||||
|
data.id
|
||||||
|
}
|
||||||
left={true}
|
left={true}
|
||||||
type={data.node.template[t].type}
|
type={data.node.template[t].type}
|
||||||
|
optionalHandle={data.node.template[t].input_types}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
@ -208,19 +237,23 @@ export default function GenericNode({
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
Object.keys(data.node.template).length < 1 ? "hidden" : "",
|
Object.keys(data.node.template).length < 1 ? "hidden" : "",
|
||||||
"flex w-full justify-center"
|
"flex-max-width justify-center"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
|
{/* <div className="px-5 py-2 mt-2 text-center">
|
||||||
Output
|
Output
|
||||||
</div> */}
|
</div> */}
|
||||||
<ParameterComponent
|
<ParameterComponent
|
||||||
data={data}
|
data={data}
|
||||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||||
title={data.type}
|
title={
|
||||||
tooltipTitle={`${data.node.base_classes.join("\n")}`}
|
data.node.output_types && data.node.output_types.length > 0
|
||||||
|
? data.node.output_types.join("|")
|
||||||
|
: data.type
|
||||||
|
}
|
||||||
|
tooltipTitle={data.node.base_classes.join("\n")}
|
||||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||||
type={data.node.base_classes.join("|")}
|
type={data.node.base_classes.join("|")}
|
||||||
left={false}
|
left={false}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
import {
|
|
||||||
XCircleIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
InformationCircleIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
|
import { CheckCircle2, Info, X, XCircle } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||||
|
|
||||||
export default function SingleAlert({
|
export default function SingleAlert({
|
||||||
|
|
@ -30,21 +25,18 @@ export default function SingleAlert({
|
||||||
>
|
>
|
||||||
{type === "error" ? (
|
{type === "error" ? (
|
||||||
<div
|
<div
|
||||||
className="flex bg-red-50 dark:bg-red-900 rounded-md p-3 mb-2 mx-2"
|
className="mx-2 mb-2 flex rounded-md bg-error-background p-3"
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon
|
<XCircle className="h-5 w-5 text-status-red" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-red-400 dark:text-red-50"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm break-words font-medium text-red-800 dark:text-white/80">
|
<h3 className="break-words text-sm font-medium text-error-foreground">
|
||||||
{dropItem.title}
|
{dropItem.title}
|
||||||
</h3>
|
</h3>
|
||||||
{dropItem.list ? (
|
{dropItem.list ? (
|
||||||
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
<div className="mt-2 text-sm text-error-foreground">
|
||||||
<ul className="list-disc space-y-1 pl-5">
|
<ul className="list-disc space-y-1 pl-5">
|
||||||
{dropItem.list.map((item, idx) => (
|
{dropItem.list.map((item, idx) => (
|
||||||
<li className="break-words" key={idx}>
|
<li className="break-words" key={idx}>
|
||||||
|
|
@ -67,34 +59,34 @@ export default function SingleAlert({
|
||||||
removeAlert(dropItem.id);
|
removeAlert(dropItem.id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-red-50 dark:bg-transparent p-1.5 text-red-500 dark:text-red-50"
|
className="inline-flex rounded-md p-1.5 text-status-red"
|
||||||
>
|
>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
<X
|
||||||
|
className="h-4 w-4 text-error-foreground"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : type === "notice" ? (
|
) : type === "notice" ? (
|
||||||
<div
|
<div
|
||||||
className="flex rounded-md bg-blue-50 dark:bg-blue-900 p-3 mb-2 mx-2"
|
className="mx-2 mb-2 flex rounded-md bg-info-background p-3"
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<Info className="h-5 w-5 text-status-blue " aria-hidden="true" />
|
||||||
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p className="text-sm text-blue-700 dark:text-white/80">
|
<p className="text-sm font-medium text-info-foreground">
|
||||||
{dropItem.title}
|
{dropItem.title}
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
<p className="mt-3 text-sm md:ml-6 md:mt-0">
|
||||||
{dropItem.link ? (
|
{dropItem.link ? (
|
||||||
<Link
|
<Link
|
||||||
to={dropItem.link}
|
to={dropItem.link}
|
||||||
className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 dark:hover:text-blue-100 hover:text-ring"
|
className="whitespace-nowrap font-medium text-info-foreground hover:text-accent-foreground"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -113,27 +105,30 @@ export default function SingleAlert({
|
||||||
removeAlert(dropItem.id);
|
removeAlert(dropItem.id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-blue-50 dark:bg-transparent p-1.5 text-blue-500 dark:text-blue-50"
|
className="inline-flex rounded-md p-1.5 text-info-foreground"
|
||||||
>
|
>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
<X
|
||||||
|
className="h-4 w-4 text-info-foreground"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="flex bg-green-50 dark:bg-green-900 p-3 mb-2 mx-2 rounded-md"
|
className="mx-2 mb-2 flex rounded-md bg-success-background p-3"
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircle2
|
||||||
className="h-5 w-5 text-green-400 dark:text-green-50"
|
className="h-5 w-5 text-status-green"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<p className="text-sm font-medium text-green-800 dark:bg-white/80">
|
<p className="text-sm font-medium text-success-foreground">
|
||||||
{dropItem.title}
|
{dropItem.title}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -147,10 +142,13 @@ export default function SingleAlert({
|
||||||
removeAlert(dropItem.id);
|
removeAlert(dropItem.id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="inline-flex rounded-md bg-green-50 dark:bg-transparent p-1.5 text-green-500 dark:text-green-50"
|
className="inline-flex rounded-md p-1.5 text-status-green"
|
||||||
>
|
>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
<X
|
||||||
|
className="h-4 w-4 text-success-foreground"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { Trash2, X } from "lucide-react";
|
||||||
|
import { useContext, useRef } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
|
||||||
import SingleAlert from "./components/singleAlertComponent";
|
|
||||||
import { AlertDropdownType } from "../../types/alerts";
|
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
import { AlertDropdownType } from "../../types/alerts";
|
||||||
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||||
|
import SingleAlert from "./components/singleAlertComponent";
|
||||||
|
|
||||||
export default function AlertDropdown({}: AlertDropdownType) {
|
export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const componentRef = useRef<HTMLDivElement>(null);
|
const componentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -24,29 +24,29 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={componentRef}
|
ref={componentRef}
|
||||||
className="z-10 py-3 pb-4 px-2 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[400px] h-[500px] flex flex-col"
|
className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-muted px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
>
|
>
|
||||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
|
||||||
Notifications
|
Notifications
|
||||||
<div className="flex gap-3 pr-3 ">
|
<div className="flex gap-3 pr-3 ">
|
||||||
<button
|
<button
|
||||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
className="text-foreground hover:text-status-red"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closePopUp();
|
closePopUp();
|
||||||
setTimeout(clearNotificationList, 100);
|
setTimeout(clearNotificationList, 100);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
<Trash2 className="h-[1.1rem] w-[1.1rem]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
className="text-foreground hover:text-status-red"
|
||||||
onClick={closePopUp}
|
onClick={closePopUp}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
<div className="text-high-foreground mt-3 flex h-full w-full flex-col overflow-y-scroll scrollbar-hide">
|
||||||
{notificationList.length !== 0 ? (
|
{notificationList.length !== 0 ? (
|
||||||
notificationList.map((alertItem, index) => (
|
notificationList.map((alertItem, index) => (
|
||||||
<SingleAlert
|
<SingleAlert
|
||||||
|
|
@ -56,7 +56,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
<div className="flex h-full w-full items-center justify-center pb-16 text-ring">
|
||||||
No new notifications
|
No new notifications
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { XCircle } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ErrorAlertType } from "../../types/alerts";
|
import { ErrorAlertType } from "../../types/alerts";
|
||||||
|
|
||||||
|
|
@ -20,6 +20,7 @@ export default function ErrorAlert({
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}, [id, removeAlert, show]);
|
}, [id, removeAlert, show]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
className="relative"
|
className="relative"
|
||||||
|
|
@ -39,22 +40,21 @@ export default function ErrorAlert({
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}, 500);
|
}, 500);
|
||||||
}}
|
}}
|
||||||
className="rounded-md w-96 mt-6 shadow-xl bg-red-50 dark:bg-red-900 p-4 cursor-pointer"
|
className="error-build-message"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon
|
<XCircle
|
||||||
className="h-5 w-5 text-red-400 dark:text-red-50"
|
className="error-build-message-circle"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-red-800 dark:text-white/80">
|
<h3 className="error-build-foreground">{title}</h3>
|
||||||
{title}
|
{list?.length !== 0 &&
|
||||||
</h3>
|
list?.some((item) => item !== null && item !== undefined) ? (
|
||||||
{list.length !== 0 ? (
|
<div className="error-build-message-div">
|
||||||
<div className="mt-2 text-sm text-red-700 dark:text-red-50">
|
<ul className="error-build-message-list">
|
||||||
<ul className="list-disc space-y-1 pl-5">
|
|
||||||
{list.map((item, index) => (
|
{list.map((item, index) => (
|
||||||
<li key={index}>{item}</li>
|
<li key={index}>{item}</li>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { Info } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { NoticeAlertType } from "../../types/alerts";
|
import { NoticeAlertType } from "../../types/alerts";
|
||||||
|
|
@ -36,22 +36,19 @@ export default function NoticeAlert({
|
||||||
setShow(false);
|
setShow(false);
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}}
|
}}
|
||||||
className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 dark:bg-blue-900 p-4"
|
className="mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<Info className="h-5 w-5 text-status-blue " aria-hidden="true" />
|
||||||
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||||
<p className="text-sm text-blue-700 dark:text-white/80">{title}</p>
|
<p className="text-sm text-info-foreground">{title}</p>
|
||||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
<p className="mt-3 text-sm md:ml-6 md:mt-0">
|
||||||
{link !== "" ? (
|
{link !== "" ? (
|
||||||
<Link
|
<Link
|
||||||
to={link}
|
to={link}
|
||||||
className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 hover:dark:text-blue-10 hover:text-ring"
|
className="whitespace-nowrap font-medium text-info-foreground hover:text-accent-foreground"
|
||||||
>
|
>
|
||||||
Details
|
Details
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
import { CheckCircle2 } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { SuccessAlertType } from "../../types/alerts";
|
import { SuccessAlertType } from "../../types/alerts";
|
||||||
|
|
||||||
|
|
@ -34,19 +34,14 @@ export default function SuccessAlert({
|
||||||
setShow(false);
|
setShow(false);
|
||||||
removeAlert(id);
|
removeAlert(id);
|
||||||
}}
|
}}
|
||||||
className="rounded-md w-96 mt-6 shadow-xl bg-green-50 dark:bg-green-900 p-4"
|
className="success-alert"
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircle2 className="success-alert-icon" aria-hidden="true" />
|
||||||
className="h-5 w-5 text-green-400 dark:text-green-50"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<p className="text-sm font-medium text-green-800 dark:text-white/80">
|
<p className="success-alert-message">{title}</p>
|
||||||
{title}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
BIN
src/frontend/src/assets/male-technologist.png
Normal file
BIN
src/frontend/src/assets/male-technologist.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
src/frontend/src/assets/robot.png
Normal file
BIN
src/frontend/src/assets/robot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
51
src/frontend/src/components/AccordionComponent/index.tsx
Normal file
51
src/frontend/src/components/AccordionComponent/index.tsx
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from "../../components/ui/accordion";
|
||||||
|
import { AccordionComponentType } from "../../types/components";
|
||||||
|
|
||||||
|
export default function AccordionComponent({
|
||||||
|
trigger,
|
||||||
|
children,
|
||||||
|
open = [],
|
||||||
|
}: AccordionComponentType) {
|
||||||
|
const [value, setValue] = useState(
|
||||||
|
open.length === 0 ? "" : getOpenAccordion()
|
||||||
|
);
|
||||||
|
|
||||||
|
function getOpenAccordion() {
|
||||||
|
let value = "";
|
||||||
|
open.forEach((el) => {
|
||||||
|
if (el == trigger) {
|
||||||
|
value = trigger;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
value == "" ? setValue(trigger) : setValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Accordion type="single" value={value} onValueChange={setValue}>
|
||||||
|
<AccordionItem value={trigger} className="border-none">
|
||||||
|
<AccordionTrigger
|
||||||
|
onClick={() => {
|
||||||
|
handleClick();
|
||||||
|
}}
|
||||||
|
className="ml-3"
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>{children}</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
|
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-foreground bg-opacity-50">
|
||||||
<div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly">
|
<div className="flex h-1/3 min-h-fit max-w-4xl flex-col justify-evenly rounded-lg bg-background p-8 text-start shadow-lg">
|
||||||
<h1 className="text-red-500 text-3xl mb-4">
|
<h1 className="mb-4 text-3xl text-status-red">
|
||||||
Oops! An unknown error has occurred.
|
Oops! An unknown error has occurred.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-700 mb-4 text-xl">
|
<p className="mb-4 text-xl text-foreground">
|
||||||
Please click the 'Reset Application' button to restore the
|
Please click the 'Reset Application' button to restore the
|
||||||
application's state. If the error persists, please create an issue on
|
application's state. If the error persists, please create an issue on
|
||||||
our GitHub page. We apologize for any inconvenience this may have
|
our GitHub page. We apologize for any inconvenience this may have
|
||||||
|
|
@ -14,7 +14,7 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={resetErrorBoundary}
|
onClick={resetErrorBoundary}
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-4"
|
className="mr-4 rounded bg-primary px-4 py-2 font-bold text-background hover:bg-ring"
|
||||||
>
|
>
|
||||||
Reset Application
|
Reset Application
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -22,7 +22,7 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
||||||
href="https://github.com/logspace-ai/langflow/issues/new"
|
href="https://github.com/logspace-ai/langflow/issues/new"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
|
className="rounded bg-status-red px-4 py-2 font-bold text-background hover:bg-error-foreground"
|
||||||
>
|
>
|
||||||
Create Issue
|
Create Issue
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useState, ChangeEvent } from "react";
|
import React, { ChangeEvent, useState } from "react";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
|
||||||
import { Label } from "../../components/ui/label";
|
|
||||||
import { Input } from "../../components/ui/input";
|
import { Input } from "../../components/ui/input";
|
||||||
|
import { Label } from "../../components/ui/label";
|
||||||
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
|
|
||||||
type InputProps = {
|
type InputProps = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
|
|
@ -44,12 +44,10 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Label>
|
<Label>
|
||||||
<div className="flex justify-between">
|
<div className="edit-flow-arrangement">
|
||||||
<span className="font-medium">Name</span>{" "}
|
<span className="font-medium">Name</span>{" "}
|
||||||
{isMaxLength && (
|
{isMaxLength && (
|
||||||
<span className="text-red-500 animate-pulse ml-10">
|
<span className="edit-flow-span">Character limit reached</span>
|
||||||
Character limit reached
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
|
|
@ -71,7 +69,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
onChange={handleDescriptionChange}
|
onChange={handleDescriptionChange}
|
||||||
value={description ?? ""}
|
value={description ?? ""}
|
||||||
placeholder="Flow description"
|
placeholder="Flow description"
|
||||||
className="max-h-[100px] mt-2 font-normal"
|
className="mt-2 max-h-[100px] font-normal"
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
|
||||||
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