Merge remote-tracking branch 'origin/dev' into types_refactor
This commit is contained in:
commit
ec79cc43bd
170 changed files with 3755 additions and 2142 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.venv/
|
||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
|
@ -14,9 +14,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
if_release:
|
if_release:
|
||||||
if: |
|
if: ${{ (github.event.pull_request.merged == true) && contains(github.event.pull_request.labels.*.name, 'Release') }}
|
||||||
${{ github.event.pull_request.merged == true }}
|
|
||||||
&& ${{ contains(github.event.pull_request.labels.*.name, 'Release') }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
||||||
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Debug Backend",
|
"name": "Debug Backend",
|
||||||
|
|
@ -38,6 +39,15 @@
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"url": "http://localhost:3000/",
|
"url": "http://localhost:3000/",
|
||||||
"webRoot": "${workspaceRoot}/src/frontend"
|
"webRoot": "${workspaceRoot}/src/frontend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Python: Debug Tests",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"purpose": ["debug-test"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ to contributions, whether it be in the form of a new feature, improved infra, or
|
||||||
To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
|
To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow.
|
||||||
Please do not try to push directly to this repo unless you are a maintainer.
|
Please do not try to push directly to this repo unless you are a maintainer.
|
||||||
|
|
||||||
|
The branch structure is as follows:
|
||||||
|
|
||||||
|
- `main`: The stable version of Langflow
|
||||||
|
- `dev`: The development version of Langflow. This branch is used to test new features before they are merged into `main` and, as such, may be unstable.
|
||||||
|
|
||||||
## 🗺️Contributing Guidelines
|
## 🗺️Contributing Guidelines
|
||||||
|
|
||||||
## 🚩GitHub Issues
|
## 🚩GitHub Issues
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
- [HuggingFace Spaces](#huggingface-spaces)
|
- [HuggingFace Spaces](#huggingface-spaces)
|
||||||
- [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli)
|
- [🖥️ Command Line Interface (CLI)](#️-command-line-interface-cli)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Environment Variables](#environment-variables)
|
- [Environment Variables](#environment-variables)
|
||||||
- [Deployment](#deployment)
|
- [Deployment](#deployment)
|
||||||
- [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform)
|
- [Deploy Langflow on Google Cloud Platform](#deploy-langflow-on-google-cloud-platform)
|
||||||
- [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud)
|
- [Deploy Langflow on Jina AI Cloud](#deploy-langflow-on-jina-ai-cloud)
|
||||||
|
|
@ -112,7 +112,6 @@ Each option is detailed below:
|
||||||
- `--cache`: Selects the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`.
|
- `--cache`: Selects the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`.
|
||||||
- `--jcloud/--no-jcloud`: Toggles the option to deploy on Jina AI Cloud. The default is `no-jcloud`.
|
- `--jcloud/--no-jcloud`: Toggles the option to deploy on Jina AI Cloud. The default is `no-jcloud`.
|
||||||
- `--dev/--no-dev`: Toggles the development mode. The default is `no-dev`.
|
- `--dev/--no-dev`: Toggles the development mode. The default is `no-dev`.
|
||||||
- `--database-url`: Sets the database URL to connect to. If not provided, a local SQLite database will be used. Can be set using the `LANGFLOW_DATABASE_URL` environment variable.
|
|
||||||
- `--path`: Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable.
|
- `--path`: Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable.
|
||||||
- `--open-browser/--no-open-browser`: Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`.
|
- `--open-browser/--no-open-browser`: Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`.
|
||||||
- `--remove-api-keys/--no-remove-api-keys`: Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`.
|
- `--remove-api-keys/--no-remove-api-keys`: Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`.
|
||||||
|
|
@ -276,6 +275,8 @@ flow("Hey, have you heard of Langflow?")
|
||||||
|
|
||||||
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible.
|
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make Langflow more accessible.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask questions, make suggestions and showcase your projects! 🦾
|
Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask questions, make suggestions and showcase your projects! 🦾
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@ COPY ./ ./
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
||||||
|
|
||||||
CMD ["uvicorn", "langflow.main:app", "--host", "0.0.0.0", "--port", "5003", "--reload", "log-level", "debug"]
|
CMD ["uvicorn", "--factory", "src.backend.langflow.main:create_app", "--host", "0.0.0.0", "--port", "7860", "--reload", "--log-level", "debug"]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version: '3.4'
|
version: "3.4"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
|
@ -7,7 +7,12 @@ services:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./dev.Dockerfile
|
dockerfile: ./dev.Dockerfile
|
||||||
command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn langflow.main:app --host 0.0.0.0 --port 7860 --reload"]
|
command:
|
||||||
|
[
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload",
|
||||||
|
]
|
||||||
ports:
|
ports:
|
||||||
- 7860:7860
|
- 7860:7860
|
||||||
- 5678:5678
|
- 5678:5678
|
||||||
|
|
@ -22,7 +27,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/frontend/public:/home/node/app/public
|
- ./src/frontend/public:/home/node/app/public
|
||||||
- ./src/frontend/src:/home/node/app/src
|
- ./src/frontend/src:/home/node/app/src
|
||||||
- ./src/frontend/package.json:/home/node/app/package.json
|
- ./src/frontend/package.json:/home/node/app/package.json
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version: '3'
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
|
@ -9,7 +9,7 @@ services:
|
||||||
- "7860:7860"
|
- "7860:7860"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app
|
- ./:/app
|
||||||
command: bash -c "uvicorn langflow.main:app --host 0.0.0.0 --port 7860 --reload"
|
command: bash -c "uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload"
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
|
|
@ -22,7 +22,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/frontend/public:/home/node/app/public
|
- ./src/frontend/public:/home/node/app/public
|
||||||
- ./src/frontend/src:/home/node/app/src
|
- ./src/frontend/src:/home/node/app/src
|
||||||
- ./src/frontend/package.json:/home/node/app/package.json
|
- ./src/frontend/package.json:/home/node/app/package.json
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
|
@ -6,5 +6,5 @@ services:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "5003:5003"
|
- "7860:7860"
|
||||||
command: langflow --host 0.0.0.0
|
command: langflow --host 0.0.0.0
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,25 @@ Used to load [OpenAI’s](https://openai.com/) embedding models.
|
||||||
- **request_timeout:** Used to specify the maximum amount of time, in milliseconds, to wait for a response from the OpenAI API when generating embeddings for a given text.
|
- **request_timeout:** Used to specify the maximum amount of time, in milliseconds, to wait for a response from the OpenAI API when generating embeddings for a given text.
|
||||||
|
|
||||||
- **tiktoken_model_name:** Used to count the number of tokens in documents to constrain them to be under a certain limit. By default, when set to None, this will be the same as the embedding model name.
|
- **tiktoken_model_name:** Used to count the number of tokens in documents to constrain them to be under a certain limit. By default, when set to None, this will be the same as the embedding model name.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### VertexAIEmbeddings
|
||||||
|
|
||||||
|
Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) [Embeddings API](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings).
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP). It provides access, management, and development of applications and services through global data centers. To use Vertex AI PaLM, you need to have the [google-cloud-aiplatform](https://pypi.org/project/google-cloud-aiplatform/) Python package installed and credentials configured for your environment.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **credentials:** The default custom credentials (google.auth.credentials.Credentials) to use.
|
||||||
|
- **location:** The default location to use when making API calls – defaults to `us-central1`.
|
||||||
|
- **max_output_tokens:** Token limit determines the maximum amount of text output from one prompt – defaults to `128`.
|
||||||
|
- **model_name:** The name of the Vertex AI large language model – defaults to `text-bison`.
|
||||||
|
- **project:** The default GCP project to use when making Vertex API calls.
|
||||||
|
- **request_parallelism:** The amount of parallelism allowed for requests issued to VertexAI models – defaults to `5`.
|
||||||
|
- **temperature:** Tunes the degree of randomness in text generations. Should be a non-negative value – defaults to `0`.
|
||||||
|
- **top_k:** How the model selects tokens for output, the next token is selected from – defaults to `40`.
|
||||||
|
- **top_p:** Tokens are selected from most probable to least until the sum of their – defaults to `0.95`.
|
||||||
|
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
|
||||||
|
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output – defaults to `False`.
|
||||||
|
|
@ -196,3 +196,25 @@ Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP).
|
||||||
- **top_p:** Tokens are selected from most probable to least until the sum of their – defaults to `0.95`.
|
- **top_p:** Tokens are selected from most probable to least until the sum of their – defaults to `0.95`.
|
||||||
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
|
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
|
||||||
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output – defaults to `False`.
|
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output – defaults to `False`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ChatVertexAI
|
||||||
|
|
||||||
|
Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) large language models.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP). It provides access, management, and development of applications and services through global data centers. To use Vertex AI PaLM, you need to have the [google-cloud-aiplatform](https://pypi.org/project/google-cloud-aiplatform/) Python package installed and credentials configured for your environment.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **credentials:** The default custom credentials (google.auth.credentials.Credentials) to use.
|
||||||
|
- **location:** The default location to use when making API calls – defaults to `us-central1`.
|
||||||
|
- **max_output_tokens:** Token limit determines the maximum amount of text output from one prompt – defaults to `128`.
|
||||||
|
- **model_name:** The name of the Vertex AI large language model – defaults to `text-bison`.
|
||||||
|
- **project:** The default GCP project to use when making Vertex API calls.
|
||||||
|
- **request_parallelism:** The amount of parallelism allowed for requests issued to VertexAI models – defaults to `5`.
|
||||||
|
- **temperature:** Tunes the degree of randomness in text generations. Should be a non-negative value – defaults to `0`.
|
||||||
|
- **top_k:** How the model selects tokens for output, the next token is selected from – defaults to `40`.
|
||||||
|
- **top_p:** Tokens are selected from most probable to least until the sum of their – defaults to `0.95`.
|
||||||
|
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
|
||||||
|
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output – defaults to `False`.
|
||||||
|
|
@ -65,7 +65,6 @@ class DocumentProcessor(CustomComponent):
|
||||||
light: "img/document_processor.png",
|
light: "img/document_processor.png",
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: "40%",
|
|
||||||
margin: "0 auto",
|
margin: "0 auto",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
|
@ -385,19 +384,19 @@ Your structure should look something like this:
|
||||||
|
|
||||||
### Loading Custom Components
|
### Loading Custom Components
|
||||||
|
|
||||||
You can specify the path to your custom components using the _`--components-path`_ argument when running the Langflow CLI, as shown below:
|
The recommended way to load custom components is to set the _`LANGFLOW_COMPONENTS_PATH`_ environment variable to the path of your custom components directory. Then, run the Langflow CLI as usual.
|
||||||
|
|
||||||
```bash
|
|
||||||
langflow --components-path /path/to/components
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, you can set the `LANGFLOW_COMPONENTS_PATH` environment variable:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export LANGFLOW_COMPONENTS_PATH=/path/to/components
|
export LANGFLOW_COMPONENTS_PATH=/path/to/components
|
||||||
langflow
|
langflow
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, you can specify the path to your custom components using the _`--components-path`_ argument when running the Langflow CLI, as shown below:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
langflow --components-path /path/to/components
|
||||||
|
```
|
||||||
|
|
||||||
Langflow will attempt to load all of the components found in the specified directory. If a component fails to load due to errors in the component's code, Langflow will print an error message to the console but will continue loading the rest of the components.
|
Langflow will attempt to load all of the components found in the specified directory. If a component fails to load due to errors in the component's code, Langflow will print an error message to the console but will continue loading the rest of the components.
|
||||||
|
|
||||||
### Interacting with Custom Components
|
### Interacting with Custom Components
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ This guide takes you through the process of augmenting the "Basic Chat with Prom
|
||||||
|
|
||||||
8. Connect this loader to the `{context}` variable that we just added.
|
8. Connect this loader to the `{context}` variable that we just added.
|
||||||
|
|
||||||
9. In the "Web Page" field, enter "https://langflow.org/how-upload-examples".
|
9. In the "Web Page" field, enter "https://docs.langflow.org/how-upload-examples".
|
||||||
|
|
||||||
10. Now, click on "ConversationBufferMemory".
|
10. Now, click on "ConversationBufferMemory".
|
||||||
|
|
||||||
|
|
|
||||||
2
docs/static/CNAME
vendored
2
docs/static/CNAME
vendored
|
|
@ -1 +1 @@
|
||||||
langflow.org
|
docs.langflow.org
|
||||||
1160
poetry.lock
generated
1160
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "0.4.0"
|
version = "0.4.7"
|
||||||
description = "A Python package with a built-in web application"
|
description = "A Python package with a built-in web application"
|
||||||
authors = ["Logspace <contact@logspace.ai>"]
|
authors = ["Logspace <contact@logspace.ai>"]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
|
|
@ -19,7 +19,7 @@ readme = "README.md"
|
||||||
keywords = ["nlp", "langchain", "openai", "gpt", "gui"]
|
keywords = ["nlp", "langchain", "openai", "gpt", "gui"]
|
||||||
packages = [{ include = "langflow", from = "src/backend" }]
|
packages = [{ include = "langflow", from = "src/backend" }]
|
||||||
include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
|
include = ["src/backend/langflow/*", "src/backend/langflow/**/*"]
|
||||||
|
documentation = "https://docs.langflow.org"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
langflow = "langflow.__main__:main"
|
langflow = "langflow.__main__:main"
|
||||||
|
|
@ -33,7 +33,7 @@ google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
gunicorn = "^21.1.0"
|
gunicorn = "^21.1.0"
|
||||||
langchain = "^0.0.249"
|
langchain = "^0.0.256"
|
||||||
openai = "^0.27.8"
|
openai = "^0.27.8"
|
||||||
pandas = "^2.0.0"
|
pandas = "^2.0.0"
|
||||||
chromadb = "^0.3.21"
|
chromadb = "^0.3.21"
|
||||||
|
|
@ -45,7 +45,7 @@ unstructured = "^0.7.0"
|
||||||
pypdf = "^3.11.0"
|
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.2.1"
|
||||||
docstring-parser = "^0.15"
|
docstring-parser = "^0.15"
|
||||||
psycopg2-binary = "^2.9.6"
|
psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^12.0.0"
|
pyarrow = "^12.0.0"
|
||||||
|
|
@ -63,7 +63,7 @@ python-multipart = "^0.0.6"
|
||||||
sqlmodel = "^0.0.8"
|
sqlmodel = "^0.0.8"
|
||||||
faiss-cpu = "^1.7.4"
|
faiss-cpu = "^1.7.4"
|
||||||
anthropic = "^0.3.0"
|
anthropic = "^0.3.0"
|
||||||
orjson = "^3.9.1"
|
orjson = "3.9.3"
|
||||||
multiprocess = "^0.70.14"
|
multiprocess = "^0.70.14"
|
||||||
cachetools = "^5.3.1"
|
cachetools = "^5.3.1"
|
||||||
types-cachetools = "^5.3.0.5"
|
types-cachetools = "^5.3.0.5"
|
||||||
|
|
@ -76,6 +76,9 @@ google-cloud-aiplatform = "^1.26.1"
|
||||||
psycopg = "^3.1.9"
|
psycopg = "^3.1.9"
|
||||||
psycopg-binary = "^3.1.9"
|
psycopg-binary = "^3.1.9"
|
||||||
fastavro = "^1.8.0"
|
fastavro = "^1.8.0"
|
||||||
|
langchain-experimental = "^0.0.8"
|
||||||
|
alembic = "^1.11.2"
|
||||||
|
metaphor-python = "^0.1.11"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@ RUN rm *.whl
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "80", "langflow.backend.app:app" ]
|
CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "7860", "--factory", "langflow.main:create_app" ]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from importlib import metadata
|
from importlib import metadata
|
||||||
from langflow.cache import cache_manager
|
|
||||||
|
# Deactivate cache manager for now
|
||||||
|
# from langflow.services.cache import cache_manager
|
||||||
from langflow.processing.process import load_flow_from_json
|
from langflow.processing.process import load_flow_from_json
|
||||||
from langflow.interface.custom.custom_component import CustomComponent
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import httpx
|
import httpx
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.utils.util import get_number_of_workers
|
from langflow.utils.util import get_number_of_workers
|
||||||
from multiprocess import Process # type: ignore
|
from multiprocess import Process # type: ignore
|
||||||
import platform
|
import platform
|
||||||
|
|
@ -13,7 +13,6 @@ from rich import box
|
||||||
from rich import print as rprint
|
from rich import print as rprint
|
||||||
import typer
|
import typer
|
||||||
from langflow.main import setup_app
|
from langflow.main import setup_app
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.utils.logger import configure, logger
|
from langflow.utils.logger import configure, logger
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
@ -25,49 +24,25 @@ def update_settings(
|
||||||
config: str,
|
config: str,
|
||||||
cache: str,
|
cache: str,
|
||||||
dev: bool = False,
|
dev: bool = False,
|
||||||
database_url: Optional[str] = None,
|
|
||||||
remove_api_keys: bool = False,
|
remove_api_keys: bool = False,
|
||||||
components_path: Optional[Path] = None,
|
components_path: Optional[Path] = None,
|
||||||
):
|
):
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
|
|
||||||
# Check for database_url in the environment variables
|
# Check for database_url in the environment variables
|
||||||
database_url = database_url or os.getenv("langflow_database_url")
|
settings_manager = get_settings_manager()
|
||||||
|
|
||||||
if config:
|
if config:
|
||||||
logger.debug(f"Loading settings from {config}")
|
logger.debug(f"Loading settings from {config}")
|
||||||
settings.update_from_yaml(config, dev=dev)
|
settings_manager.settings.update_from_yaml(config, dev=dev)
|
||||||
if database_url:
|
|
||||||
settings.update_settings(database_url=database_url)
|
|
||||||
if remove_api_keys:
|
if remove_api_keys:
|
||||||
logger.debug(f"Setting remove_api_keys to {remove_api_keys}")
|
logger.debug(f"Setting remove_api_keys to {remove_api_keys}")
|
||||||
settings.update_settings(remove_api_keys=remove_api_keys)
|
settings_manager.settings.update_settings(REMOVE_API_KEYS=remove_api_keys)
|
||||||
if cache:
|
if cache:
|
||||||
logger.debug(f"Setting cache to {cache}")
|
logger.debug(f"Setting cache to {cache}")
|
||||||
settings.update_settings(cache=cache)
|
settings_manager.settings.update_settings(CACHE=cache)
|
||||||
if components_path:
|
if components_path:
|
||||||
logger.debug(f"Adding component path {components_path}")
|
logger.debug(f"Adding component path {components_path}")
|
||||||
settings.update_settings(components_path=components_path)
|
settings_manager.settings.update_settings(COMPONENTS_PATH=components_path)
|
||||||
|
|
||||||
|
|
||||||
def load_params():
|
|
||||||
"""
|
|
||||||
Load the parameters from the environment variables.
|
|
||||||
"""
|
|
||||||
global_vars = globals()
|
|
||||||
|
|
||||||
for key, value in global_vars.items():
|
|
||||||
env_key = f"LANGFLOW_{key.upper()}"
|
|
||||||
if env_key in os.environ:
|
|
||||||
if isinstance(value, bool):
|
|
||||||
# Handle booleans
|
|
||||||
global_vars[key] = os.getenv(env_key, str(value)).lower() == "true"
|
|
||||||
elif isinstance(value, int):
|
|
||||||
# Handle integers
|
|
||||||
global_vars[key] = int(os.getenv(env_key, str(value)))
|
|
||||||
elif isinstance(value, str) or value is None:
|
|
||||||
# Handle strings and None values
|
|
||||||
global_vars[key] = os.getenv(env_key, str(value))
|
|
||||||
|
|
||||||
|
|
||||||
def serve_on_jcloud():
|
def serve_on_jcloud():
|
||||||
|
|
@ -131,10 +106,12 @@ def serve(
|
||||||
help="Path to the directory containing custom components.",
|
help="Path to the directory containing custom components.",
|
||||||
envvar="LANGFLOW_COMPONENTS_PATH",
|
envvar="LANGFLOW_COMPONENTS_PATH",
|
||||||
),
|
),
|
||||||
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
|
config: str = typer.Option(
|
||||||
|
Path(__file__).parent / "config.yaml", help="Path to the configuration file."
|
||||||
|
),
|
||||||
# .env file param
|
# .env file param
|
||||||
env_file: Path = typer.Option(
|
env_file: Path = typer.Option(
|
||||||
".env", help="Path to the .env file containing environment variables."
|
None, help="Path to the .env file containing environment variables."
|
||||||
),
|
),
|
||||||
log_level: str = typer.Option(
|
log_level: str = typer.Option(
|
||||||
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
||||||
|
|
@ -149,11 +126,13 @@ def serve(
|
||||||
),
|
),
|
||||||
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
|
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
|
||||||
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
|
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
|
||||||
database_url: str = typer.Option(
|
# This variable does not work but is set by the .env file
|
||||||
None,
|
# and works with Pydantic
|
||||||
help="Database URL to connect to. If not provided, a local SQLite database will be used.",
|
# database_url: str = typer.Option(
|
||||||
envvar="LANGFLOW_DATABASE_URL",
|
# None,
|
||||||
),
|
# help="Database URL to connect to. If not provided, a local SQLite database will be used.",
|
||||||
|
# envvar="LANGFLOW_DATABASE_URL",
|
||||||
|
# ),
|
||||||
path: str = typer.Option(
|
path: str = typer.Option(
|
||||||
None,
|
None,
|
||||||
help="Path to the frontend directory containing build files. This is for development purposes only.",
|
help="Path to the frontend directory containing build files. This is for development purposes only.",
|
||||||
|
|
@ -169,6 +148,11 @@ def serve(
|
||||||
help="Remove API keys from the projects saved in the database.",
|
help="Remove API keys from the projects saved in the database.",
|
||||||
envvar="LANGFLOW_REMOVE_API_KEYS",
|
envvar="LANGFLOW_REMOVE_API_KEYS",
|
||||||
),
|
),
|
||||||
|
backend_only: bool = typer.Option(
|
||||||
|
False,
|
||||||
|
help="Run only the backend server without the frontend.",
|
||||||
|
envvar="LANGFLOW_BACKEND_ONLY",
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run the Langflow server.
|
Run the Langflow server.
|
||||||
|
|
@ -176,7 +160,6 @@ def serve(
|
||||||
# override env variables with .env file
|
# override env variables with .env file
|
||||||
if env_file:
|
if env_file:
|
||||||
load_dotenv(env_file, override=True)
|
load_dotenv(env_file, override=True)
|
||||||
load_params()
|
|
||||||
|
|
||||||
if jcloud:
|
if jcloud:
|
||||||
return serve_on_jcloud()
|
return serve_on_jcloud()
|
||||||
|
|
@ -185,14 +168,13 @@ def serve(
|
||||||
update_settings(
|
update_settings(
|
||||||
config,
|
config,
|
||||||
dev=dev,
|
dev=dev,
|
||||||
database_url=database_url,
|
|
||||||
remove_api_keys=remove_api_keys,
|
remove_api_keys=remove_api_keys,
|
||||||
cache=cache,
|
cache=cache,
|
||||||
components_path=components_path,
|
components_path=components_path,
|
||||||
)
|
)
|
||||||
# create path object if path is provided
|
# create path object if path is provided
|
||||||
static_files_dir: Optional[Path] = Path(path) if path else None
|
static_files_dir: Optional[Path] = Path(path) if path else None
|
||||||
app = setup_app(static_files_dir=static_files_dir)
|
app = setup_app(static_files_dir=static_files_dir, backend_only=backend_only)
|
||||||
# check if port is being used
|
# check if port is being used
|
||||||
if is_port_in_use(port, host):
|
if is_port_in_use(port, host):
|
||||||
port = get_free_port(port)
|
port = get_free_port(port)
|
||||||
|
|
@ -204,6 +186,10 @@ def serve(
|
||||||
"timeout": timeout,
|
"timeout": timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Define an env variable to know if we are just testing the server
|
||||||
|
if "pytest" in sys.modules:
|
||||||
|
return
|
||||||
|
|
||||||
if platform.system() in ["Windows"]:
|
if platform.system() in ["Windows"]:
|
||||||
# Run using uvicorn on MacOS and Windows
|
# Run using uvicorn on MacOS and Windows
|
||||||
# Windows doesn't support gunicorn
|
# Windows doesn't support gunicorn
|
||||||
|
|
|
||||||
113
src/backend/langflow/alembic.ini
Normal file
113
src/backend/langflow/alembic.ini
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# path to migration scripts
|
||||||
|
script_location = alembic
|
||||||
|
|
||||||
|
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||||
|
# Uncomment the line below if you want the files to be prepended with date and time
|
||||||
|
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||||
|
# for all available tokens
|
||||||
|
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# sys.path path, will be prepended to sys.path if present.
|
||||||
|
# defaults to the current working directory.
|
||||||
|
prepend_sys_path = .
|
||||||
|
|
||||||
|
# timezone to use when rendering the date within the migration file
|
||||||
|
# as well as the filename.
|
||||||
|
# If specified, requires the python-dateutil library that can be
|
||||||
|
# installed by adding `alembic[tz]` to the pip requirements
|
||||||
|
# string value is passed to dateutil.tz.gettz()
|
||||||
|
# leave blank for localtime
|
||||||
|
# timezone =
|
||||||
|
|
||||||
|
# max length of characters to apply to the
|
||||||
|
# "slug" field
|
||||||
|
# truncate_slug_length = 40
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
# set to 'true' to allow .pyc and .pyo files without
|
||||||
|
# a source .py file to be detected as revisions in the
|
||||||
|
# versions/ directory
|
||||||
|
# sourceless = false
|
||||||
|
|
||||||
|
# version location specification; This defaults
|
||||||
|
# to alembic/versions. When using multiple version
|
||||||
|
# directories, initial revisions must be specified with --version-path.
|
||||||
|
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||||
|
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
||||||
|
|
||||||
|
# version path separator; As mentioned above, this is the character used to split
|
||||||
|
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||||
|
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||||
|
# Valid values for version_path_separator are:
|
||||||
|
#
|
||||||
|
# version_path_separator = :
|
||||||
|
# version_path_separator = ;
|
||||||
|
# version_path_separator = space
|
||||||
|
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||||
|
|
||||||
|
# set to 'true' to search source files recursively
|
||||||
|
# in each "version_locations" directory
|
||||||
|
# new in Alembic version 1.10
|
||||||
|
# recursive_version_locations = false
|
||||||
|
|
||||||
|
# the output encoding used when revision files
|
||||||
|
# are written from script.py.mako
|
||||||
|
# output_encoding = utf-8
|
||||||
|
|
||||||
|
# This is the path to the db in the root of the project.
|
||||||
|
# When the user runs the Langflow the database url will
|
||||||
|
# be set dinamically.
|
||||||
|
sqlalchemy.url = sqlite:///../../../langflow.db
|
||||||
|
|
||||||
|
|
||||||
|
[post_write_hooks]
|
||||||
|
# post_write_hooks defines scripts or Python functions that are run
|
||||||
|
# on newly generated revision scripts. See the documentation for further
|
||||||
|
# detail and examples
|
||||||
|
|
||||||
|
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||||
|
# hooks = black
|
||||||
|
# black.type = console_scripts
|
||||||
|
# black.entrypoint = black
|
||||||
|
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
||||||
1
src/backend/langflow/alembic/README
Normal file
1
src/backend/langflow/alembic/README
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Generic single-database configuration.
|
||||||
78
src/backend/langflow/alembic/env.py
Normal file
78
src/backend/langflow/alembic/env.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
|
||||||
|
from langflow.services.database.manager import SQLModel
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
if config.config_file_name is not None:
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
target_metadata = SQLModel.metadata
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline() -> None:
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online() -> None:
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
connectable = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section, {}),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(connection=connection, target_metadata=target_metadata)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
27
src/backend/langflow/alembic/script.py.mako
Normal file
27
src/backend/langflow/alembic/script.py.mako
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = ${repr(up_revision)}
|
||||||
|
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||||
|
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
"""Remove FlowStyles table
|
||||||
|
|
||||||
|
Revision ID: 0a534bdfd84b
|
||||||
|
Revises: 4814b6f4abfd
|
||||||
|
Create Date: 2023-08-07 14:09:06.844104
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "0a534bdfd84b"
|
||||||
|
down_revision: Union[str, None] = "4814b6f4abfd"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("flowstyle")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"flowstyle",
|
||||||
|
sa.Column("color", sa.VARCHAR(), nullable=False),
|
||||||
|
sa.Column("emoji", sa.VARCHAR(), nullable=False),
|
||||||
|
sa.Column("flow_id", sa.CHAR(length=32), nullable=True),
|
||||||
|
sa.Column("id", sa.CHAR(length=32), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["flow_id"],
|
||||||
|
["flow.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""Add Flow table
|
||||||
|
|
||||||
|
Revision ID: 4814b6f4abfd
|
||||||
|
Revises:
|
||||||
|
Create Date: 2023-08-05 17:47:42.879824
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "4814b6f4abfd"
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
|
||||||
|
# This suppress is used to not break the migration if the table already exists.
|
||||||
|
with contextlib.suppress(sa.exc.OperationalError):
|
||||||
|
op.create_table(
|
||||||
|
"flow",
|
||||||
|
sa.Column("data", sa.JSON(), nullable=True),
|
||||||
|
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_flow_description"), "flow", ["description"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_flow_name"), "flow", ["name"], unique=False)
|
||||||
|
with contextlib.suppress(sa.exc.OperationalError):
|
||||||
|
op.create_table(
|
||||||
|
"flowstyle",
|
||||||
|
sa.Column("color", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column("emoji", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column("flow_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
|
||||||
|
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["flow_id"],
|
||||||
|
["flow.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("flowstyle")
|
||||||
|
op.drop_index(op.f("ix_flow_name"), table_name="flow")
|
||||||
|
op.drop_index(op.f("ix_flow_description"), table_name="flow")
|
||||||
|
op.drop_table("flow")
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -5,7 +5,6 @@ from langflow.api.v1 import (
|
||||||
endpoints_router,
|
endpoints_router,
|
||||||
validate_router,
|
validate_router,
|
||||||
flows_router,
|
flows_router,
|
||||||
flow_styles_router,
|
|
||||||
component_router,
|
component_router,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -17,4 +16,3 @@ router.include_router(endpoints_router)
|
||||||
router.include_router(validate_router)
|
router.include_router(validate_router)
|
||||||
router.include_router(component_router)
|
router.include_router(component_router)
|
||||||
router.include_router(flows_router)
|
router.include_router(flows_router)
|
||||||
router.include_router(flow_styles_router)
|
|
||||||
|
|
|
||||||
|
|
@ -66,3 +66,30 @@ def merge_nested_dicts(dict1, dict2):
|
||||||
else:
|
else:
|
||||||
dict1[key] = value
|
dict1[key] = value
|
||||||
return dict1
|
return dict1
|
||||||
|
|
||||||
|
|
||||||
|
def merge_nested_dicts_with_renaming(dict1, dict2):
|
||||||
|
for key, value in dict2.items():
|
||||||
|
if (
|
||||||
|
key in dict1
|
||||||
|
and isinstance(value, dict)
|
||||||
|
and isinstance(dict1.get(key), dict)
|
||||||
|
):
|
||||||
|
for sub_key, sub_value in value.items():
|
||||||
|
if sub_key in dict1[key]:
|
||||||
|
new_key = get_new_key(dict1[key], sub_key)
|
||||||
|
dict1[key][new_key] = sub_value
|
||||||
|
else:
|
||||||
|
dict1[key][sub_key] = sub_value
|
||||||
|
else:
|
||||||
|
dict1[key] = value
|
||||||
|
return dict1
|
||||||
|
|
||||||
|
|
||||||
|
def get_new_key(dictionary, original_key):
|
||||||
|
counter = 1
|
||||||
|
new_key = original_key + " (" + str(counter) + ")"
|
||||||
|
while new_key in dictionary:
|
||||||
|
counter += 1
|
||||||
|
new_key = original_key + " (" + str(counter) + ")"
|
||||||
|
return new_key
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ from langflow.api.v1.endpoints import router as endpoints_router
|
||||||
from langflow.api.v1.validate import router as validate_router
|
from langflow.api.v1.validate import router as validate_router
|
||||||
from langflow.api.v1.chat import router as chat_router
|
from langflow.api.v1.chat import router as chat_router
|
||||||
from langflow.api.v1.flows import router as flows_router
|
from langflow.api.v1.flows import router as flows_router
|
||||||
from langflow.api.v1.flow_styles import router as flow_styles_router
|
|
||||||
from langflow.api.v1.components import router as component_router
|
from langflow.api.v1.components import router as component_router
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
@ -11,5 +10,4 @@ __all__ = [
|
||||||
"component_router",
|
"component_router",
|
||||||
"validate_router",
|
"validate_router",
|
||||||
"flows_router",
|
"flows_router",
|
||||||
"flow_styles_router",
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ from fastapi.responses import StreamingResponse
|
||||||
from langflow.api.utils import build_input_keys_response
|
from langflow.api.utils import build_input_keys_response
|
||||||
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
||||||
|
|
||||||
from langflow.chat.manager import ChatManager
|
from langflow.services import service_manager, ServiceType
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from cachetools import LRUCache
|
from cachetools import LRUCache
|
||||||
|
|
||||||
router = APIRouter(tags=["Chat"])
|
router = APIRouter(tags=["Chat"])
|
||||||
chat_manager = ChatManager()
|
|
||||||
flow_data_store: LRUCache = LRUCache(maxsize=10)
|
flow_data_store: LRUCache = LRUCache(maxsize=10)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,6 +17,7 @@ flow_data_store: LRUCache = LRUCache(maxsize=10)
|
||||||
async def chat(client_id: str, websocket: WebSocket):
|
async def chat(client_id: str, websocket: WebSocket):
|
||||||
"""Websocket endpoint for chat."""
|
"""Websocket endpoint for chat."""
|
||||||
try:
|
try:
|
||||||
|
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
|
||||||
if client_id in chat_manager.in_memory_cache:
|
if client_id in chat_manager.in_memory_cache:
|
||||||
await chat_manager.handle_websocket(client_id, websocket)
|
await chat_manager.handle_websocket(client_id, websocket)
|
||||||
else:
|
else:
|
||||||
|
|
@ -45,6 +46,7 @@ async def init_build(graph_data: dict, flow_id: str):
|
||||||
return InitResponse(flowId=flow_id)
|
return InitResponse(flowId=flow_id)
|
||||||
|
|
||||||
# Delete from cache if already exists
|
# Delete from cache if already exists
|
||||||
|
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
|
||||||
if flow_id in chat_manager.in_memory_cache:
|
if flow_id in chat_manager.in_memory_cache:
|
||||||
with chat_manager.in_memory_cache._lock:
|
with chat_manager.in_memory_cache._lock:
|
||||||
chat_manager.in_memory_cache.delete(flow_id)
|
chat_manager.in_memory_cache.delete(flow_id)
|
||||||
|
|
@ -125,9 +127,8 @@ async def stream_build(flow_id: str):
|
||||||
vertex.build()
|
vertex.build()
|
||||||
params = vertex._built_object_repr()
|
params = vertex._built_object_repr()
|
||||||
valid = True
|
valid = True
|
||||||
logger.debug(
|
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
||||||
f"Building node {str(params)[:50]}{'...' if len(str(params)) > 50 else ''}"
|
logger.debug(f"Output: {params}")
|
||||||
)
|
|
||||||
if vertex.artifacts:
|
if vertex.artifacts:
|
||||||
# The artifacts will be prompt variables
|
# The artifacts will be prompt variables
|
||||||
# passed to build_input_keys_response
|
# passed to build_input_keys_response
|
||||||
|
|
@ -156,12 +157,12 @@ async def stream_build(flow_id: str):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
input_keys_response = {
|
input_keys_response = {
|
||||||
"input_keys": {},
|
"input_keys": None,
|
||||||
"memory_keys": [],
|
"memory_keys": [],
|
||||||
"handle_keys": [],
|
"handle_keys": [],
|
||||||
}
|
}
|
||||||
yield str(StreamData(event="message", data=input_keys_response))
|
yield str(StreamData(event="message", data=input_keys_response))
|
||||||
|
chat_manager = service_manager.get(ServiceType.CHAT_MANAGER)
|
||||||
chat_manager.set_cache(flow_id, langchain_object)
|
chat_manager.set_cache(flow_id, langchain_object)
|
||||||
# We need to reset the chat history
|
# We need to reset the chat history
|
||||||
chat_manager.chat_history.empty_history(flow_id)
|
chat_manager.chat_history.empty_history(flow_id)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow.database.models.component import Component, ComponentModel
|
from langflow.services.database.models.component import Component, ComponentModel
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, Optional
|
from typing import Annotated, Optional
|
||||||
|
|
||||||
from langflow.cache.utils import save_uploaded_file
|
from langflow.services.cache.utils import save_uploaded_file
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.services.database.models.flow import Flow
|
||||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.settings import settings
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body
|
||||||
|
|
||||||
from langflow.interface.custom.custom_component import CustomComponent
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
|
|
||||||
from langflow.interface.custom.directory_reader import (
|
|
||||||
CustomComponentPathValueError,
|
|
||||||
)
|
|
||||||
|
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
ProcessResponse,
|
ProcessResponse,
|
||||||
|
|
@ -21,7 +17,7 @@ from langflow.api.v1.schemas import (
|
||||||
CustomComponentCode,
|
CustomComponentCode,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.api.utils import merge_nested_dicts
|
from langflow.api.utils import merge_nested_dicts_with_renaming
|
||||||
|
|
||||||
from langflow.interface.types import (
|
from langflow.interface.types import (
|
||||||
build_langchain_types_dict,
|
build_langchain_types_dict,
|
||||||
|
|
@ -29,7 +25,7 @@ from langflow.interface.types import (
|
||||||
build_langchain_custom_component_list_from_path,
|
build_langchain_custom_component_list_from_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
# build router
|
# build router
|
||||||
|
|
@ -38,48 +34,38 @@ router = APIRouter(tags=["Base"])
|
||||||
|
|
||||||
@router.get("/all")
|
@router.get("/all")
|
||||||
def get_all():
|
def get_all():
|
||||||
|
logger.debug("Building langchain types dict")
|
||||||
native_components = build_langchain_types_dict()
|
native_components = build_langchain_types_dict()
|
||||||
|
|
||||||
# custom_components is a list of dicts
|
# custom_components is a list of dicts
|
||||||
# need to merge all the keys into one dict
|
# need to merge all the keys into one dict
|
||||||
custom_components_from_file = {}
|
custom_components_from_file = {}
|
||||||
if settings.components_path:
|
settings_manager = get_settings_manager()
|
||||||
|
if settings_manager.settings.COMPONENTS_PATH:
|
||||||
|
logger.info(
|
||||||
|
f"Building custom components from {settings_manager.settings.COMPONENTS_PATH}"
|
||||||
|
)
|
||||||
custom_component_dicts = [
|
custom_component_dicts = [
|
||||||
build_langchain_custom_component_list_from_path(str(path))
|
build_langchain_custom_component_list_from_path(str(path))
|
||||||
for path in settings.components_path
|
for path in settings_manager.settings.COMPONENTS_PATH
|
||||||
]
|
]
|
||||||
|
logger.info(f"Loading {len(custom_component_dicts)} category(ies)")
|
||||||
for custom_component_dict in custom_component_dicts:
|
for custom_component_dict in custom_component_dicts:
|
||||||
custom_components_from_file = merge_nested_dicts(
|
# custom_component_dict is a dict of dicts
|
||||||
|
if not custom_component_dict:
|
||||||
|
continue
|
||||||
|
category = list(custom_component_dict.keys())[0]
|
||||||
|
logger.info(
|
||||||
|
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
||||||
|
)
|
||||||
|
logger.debug(custom_component_dict)
|
||||||
|
custom_components_from_file = merge_nested_dicts_with_renaming(
|
||||||
custom_components_from_file, custom_component_dict
|
custom_components_from_file, custom_component_dict
|
||||||
)
|
)
|
||||||
return merge_nested_dicts(native_components, custom_components_from_file)
|
|
||||||
|
|
||||||
|
return merge_nested_dicts_with_renaming(
|
||||||
@router.get("/load_custom_component_from_path")
|
native_components, custom_components_from_file
|
||||||
def get_load_custom_component_from_path(path: str):
|
|
||||||
try:
|
|
||||||
data = build_langchain_custom_component_list_from_path(path)
|
|
||||||
except CustomComponentPathValueError as err:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail={"error": type(err).__name__, "traceback": str(err)},
|
|
||||||
) from err
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/load_custom_component_from_path_TEST")
|
|
||||||
def get_load_custom_component_from_path_test(path: str):
|
|
||||||
from langflow.interface.custom.directory_reader import (
|
|
||||||
DirectoryReader,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
reader = DirectoryReader(path, False)
|
|
||||||
file_list = reader.get_files()
|
|
||||||
data = reader.build_component_menu_list(file_list)
|
|
||||||
|
|
||||||
return reader.filter_loaded_components(data, True)
|
|
||||||
|
|
||||||
|
|
||||||
# For backwards compatibility we will keep the old endpoint
|
# For backwards compatibility we will keep the old endpoint
|
||||||
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
|
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
|
||||||
|
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
from uuid import UUID
|
|
||||||
from langflow.database.models.flow_style import (
|
|
||||||
FlowStyle,
|
|
||||||
FlowStyleCreate,
|
|
||||||
FlowStyleRead,
|
|
||||||
FlowStyleUpdate,
|
|
||||||
)
|
|
||||||
from langflow.database.base import get_session
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
|
||||||
|
|
||||||
|
|
||||||
# build router
|
|
||||||
router = APIRouter(prefix="/flow_styles", tags=["FlowStyles"])
|
|
||||||
|
|
||||||
# FlowStyleCreate:
|
|
||||||
# class FlowStyleBase(SQLModel):
|
|
||||||
# color: str = Field(index=True)
|
|
||||||
# emoji: str = Field(index=False)
|
|
||||||
# flow_id: UUID = Field(default=None, foreign_key="flow.id")
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=FlowStyleRead)
|
|
||||||
def create_flow_style(
|
|
||||||
*, session: Session = Depends(get_session), flow_style: FlowStyleCreate
|
|
||||||
):
|
|
||||||
"""Create a new flow_style."""
|
|
||||||
db_flow_style = FlowStyle.from_orm(flow_style)
|
|
||||||
session.add(db_flow_style)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_flow_style)
|
|
||||||
return db_flow_style
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[FlowStyleRead])
|
|
||||||
def read_flow_styles(*, session: Session = Depends(get_session)):
|
|
||||||
"""Read all flows."""
|
|
||||||
try:
|
|
||||||
flows = session.exec(select(FlowStyle)).all()
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
|
||||||
return flows
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{flow_styles_id}", response_model=FlowStyleRead)
|
|
||||||
def read_flow_style(*, session: Session = Depends(get_session), flow_styles_id: UUID):
|
|
||||||
"""Read a flow_style."""
|
|
||||||
if flow_style := session.get(FlowStyle, flow_styles_id):
|
|
||||||
return flow_style
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=404, detail="FlowStyle not found")
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{flow_style_id}", response_model=FlowStyleRead)
|
|
||||||
def update_flow_style(
|
|
||||||
*,
|
|
||||||
session: Session = Depends(get_session),
|
|
||||||
flow_style_id: UUID,
|
|
||||||
flow_style: FlowStyleUpdate,
|
|
||||||
):
|
|
||||||
"""Update a flow_style."""
|
|
||||||
db_flow_style = session.get(FlowStyle, flow_style_id)
|
|
||||||
if not db_flow_style:
|
|
||||||
raise HTTPException(status_code=404, detail="FlowStyle not found")
|
|
||||||
flow_data = flow_style.dict(exclude_unset=True)
|
|
||||||
for key, value in flow_data.items():
|
|
||||||
if hasattr(db_flow_style, key) and value is not None:
|
|
||||||
setattr(db_flow_style, key, value)
|
|
||||||
session.add(db_flow_style)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(db_flow_style)
|
|
||||||
return db_flow_style
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{flow_id}")
|
|
||||||
def delete_flow_style(*, session: Session = Depends(get_session), flow_id: UUID):
|
|
||||||
"""Delete a flow_style."""
|
|
||||||
flow_style = session.get(FlowStyle, flow_id)
|
|
||||||
if not flow_style:
|
|
||||||
raise HTTPException(status_code=404, detail="FlowStyle not found")
|
|
||||||
session.delete(flow_style)
|
|
||||||
session.commit()
|
|
||||||
return {"message": "FlowStyle deleted successfully"}
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.api.utils import remove_api_keys
|
from langflow.api.utils import remove_api_keys
|
||||||
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
||||||
from langflow.database.models.flow import (
|
from langflow.services.database.models.flow import (
|
||||||
Flow,
|
Flow,
|
||||||
FlowCreate,
|
FlowCreate,
|
||||||
FlowRead,
|
FlowRead,
|
||||||
FlowReadWithStyle,
|
|
||||||
FlowUpdate,
|
FlowUpdate,
|
||||||
)
|
)
|
||||||
from langflow.database.base import get_session
|
from langflow.services.utils import get_session
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
|
@ -32,7 +31,7 @@ def create_flow(*, session: Session = Depends(get_session), flow: FlowCreate):
|
||||||
return db_flow
|
return db_flow
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[FlowReadWithStyle], status_code=200)
|
@router.get("/", response_model=list[FlowRead], status_code=200)
|
||||||
def read_flows(*, session: Session = Depends(get_session)):
|
def read_flows(*, session: Session = Depends(get_session)):
|
||||||
"""Read all flows."""
|
"""Read all flows."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -42,7 +41,7 @@ def read_flows(*, session: Session = Depends(get_session)):
|
||||||
return [jsonable_encoder(flow) for flow in flows]
|
return [jsonable_encoder(flow) for flow in flows]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{flow_id}", response_model=FlowReadWithStyle, status_code=200)
|
@router.get("/{flow_id}", response_model=FlowRead, status_code=200)
|
||||||
def read_flow(*, session: Session = Depends(get_session), flow_id: UUID):
|
def read_flow(*, session: Session = Depends(get_session), flow_id: UUID):
|
||||||
"""Read a flow."""
|
"""Read a flow."""
|
||||||
if flow := session.get(Flow, flow_id):
|
if flow := session.get(Flow, flow_id):
|
||||||
|
|
@ -61,7 +60,8 @@ def update_flow(
|
||||||
if not db_flow:
|
if not db_flow:
|
||||||
raise HTTPException(status_code=404, detail="Flow not found")
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||||||
flow_data = flow.dict(exclude_unset=True)
|
flow_data = flow.dict(exclude_unset=True)
|
||||||
if settings.remove_api_keys:
|
settings_manager = get_settings_manager()
|
||||||
|
if settings_manager.settings.REMOVE_API_KEYS:
|
||||||
flow_data = remove_api_keys(flow_data)
|
flow_data = remove_api_keys(flow_data)
|
||||||
for key, value in flow_data.items():
|
for key, value in flow_data.items():
|
||||||
setattr(db_flow, key, value)
|
setattr(db_flow, key, value)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
from langflow.database.models.flow import FlowCreate, FlowRead
|
from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
|
||||||
7
src/backend/langflow/cache/__init__.py
vendored
7
src/backend/langflow/cache/__init__.py
vendored
|
|
@ -1,7 +0,0 @@
|
||||||
from langflow.cache.manager import cache_manager
|
|
||||||
from langflow.cache.flow import InMemoryCache
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"cache_manager",
|
|
||||||
"InMemoryCache",
|
|
||||||
]
|
|
||||||
4
src/backend/langflow/components/__init__.py
Normal file
4
src/backend/langflow/components/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["CustomComponent"]
|
||||||
33
src/backend/langflow/components/chains/PromptRunner.py
Normal file
33
src/backend/langflow/components/chains/PromptRunner.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
from langchain.llms.base import BaseLLM
|
||||||
|
from langchain import PromptTemplate
|
||||||
|
from langchain.schema import Document
|
||||||
|
|
||||||
|
|
||||||
|
class PromptRunner(CustomComponent):
|
||||||
|
display_name: str = "Prompt Runner"
|
||||||
|
description: str = "Run a Chain with the given PromptTemplate"
|
||||||
|
beta = True
|
||||||
|
field_config = {
|
||||||
|
"llm": {"display_name": "LLM"},
|
||||||
|
"prompt": {
|
||||||
|
"display_name": "Prompt Template",
|
||||||
|
"info": "Make sure the prompt has all variables filled.",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
"inputs": {"field_type": "code"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
llm: BaseLLM,
|
||||||
|
prompt: PromptTemplate,
|
||||||
|
) -> Document:
|
||||||
|
chain = prompt | llm
|
||||||
|
# The input is an empty dict because the prompt is already filled
|
||||||
|
result = chain.invoke({})
|
||||||
|
if hasattr(result, "content"):
|
||||||
|
result = result.content
|
||||||
|
self.repr_value = result
|
||||||
|
return Document(page_content=str(result))
|
||||||
56
src/backend/langflow/components/toolkits/Metaphor.py
Normal file
56
src/backend/langflow/components/toolkits/Metaphor.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from typing import List, Union
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
from metaphor_python import Metaphor # type: ignore
|
||||||
|
from langchain.tools import Tool
|
||||||
|
from langchain.agents import tool
|
||||||
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
|
|
||||||
|
|
||||||
|
class MetaphorToolkit(CustomComponent):
|
||||||
|
display_name: str = "Metaphor"
|
||||||
|
description: str = "Metaphor Toolkit"
|
||||||
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/tools/metaphor_search"
|
||||||
|
)
|
||||||
|
beta = True
|
||||||
|
# api key should be password = True
|
||||||
|
field_config = {
|
||||||
|
"metaphor_api_key": {"display_name": "Metaphor API Key", "password": True},
|
||||||
|
"code": {"advanced": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
metaphor_api_key: str,
|
||||||
|
use_autoprompt: bool = True,
|
||||||
|
search_num_results: int = 5,
|
||||||
|
similar_num_results: int = 5,
|
||||||
|
) -> Union[Tool, BaseToolkit]:
|
||||||
|
# If documents, then we need to create a Vectara instance using .from_documents
|
||||||
|
client = Metaphor(api_key=metaphor_api_key)
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def search(query: str):
|
||||||
|
"""Call search engine with a query."""
|
||||||
|
return client.search(
|
||||||
|
query, use_autoprompt=use_autoprompt, num_results=search_num_results
|
||||||
|
)
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def get_contents(ids: List[str]):
|
||||||
|
"""Get contents of a webpage.
|
||||||
|
|
||||||
|
The ids passed in should be a list of ids as fetched from `search`.
|
||||||
|
"""
|
||||||
|
return client.get_contents(ids)
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def find_similar(url: str):
|
||||||
|
"""Get search results similar to a given URL.
|
||||||
|
|
||||||
|
The url passed in should be a URL returned from `search`
|
||||||
|
"""
|
||||||
|
return client.find_similar(url, num_results=similar_num_results)
|
||||||
|
|
||||||
|
return [search, get_contents, find_similar] # type: ignore
|
||||||
50
src/backend/langflow/components/vectorstores/Vectara.py
Normal file
50
src/backend/langflow/components/vectorstores/Vectara.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from typing import Optional, Union
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
from langchain.vectorstores import Vectara
|
||||||
|
from langchain.schema import Document
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
|
from langchain.schema import BaseRetriever
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
|
||||||
|
class VectaraComponent(CustomComponent):
|
||||||
|
display_name: str = "Vectara"
|
||||||
|
description: str = "Implementation of Vector Store using Vectara"
|
||||||
|
documentation = (
|
||||||
|
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
|
)
|
||||||
|
beta = True
|
||||||
|
# api key should be password = True
|
||||||
|
field_config = {
|
||||||
|
"vectara_customer_id": {"display_name": "Vectara Customer ID"},
|
||||||
|
"vectara_corpus_id": {"display_name": "Vectara Corpus ID"},
|
||||||
|
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
|
||||||
|
"code": {"show": False},
|
||||||
|
"documents": {"display_name": "Documents"},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
vectara_customer_id: str,
|
||||||
|
vectara_corpus_id: str,
|
||||||
|
vectara_api_key: str,
|
||||||
|
embedding: Optional[Embeddings] = None,
|
||||||
|
documents: Optional[Document] = None,
|
||||||
|
) -> Union[VectorStore, BaseRetriever]:
|
||||||
|
# If documents, then we need to create a Vectara instance using .from_documents
|
||||||
|
if documents is not None and embedding is not None:
|
||||||
|
return Vectara.from_documents(
|
||||||
|
documents=documents, # type: ignore
|
||||||
|
vectara_customer_id=vectara_customer_id,
|
||||||
|
vectara_corpus_id=vectara_corpus_id,
|
||||||
|
vectara_api_key=vectara_api_key,
|
||||||
|
embedding=embedding,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Vectara(
|
||||||
|
vectara_customer_id=vectara_customer_id,
|
||||||
|
vectara_corpus_id=vectara_corpus_id,
|
||||||
|
vectara_api_key=vectara_api_key,
|
||||||
|
)
|
||||||
|
|
@ -104,6 +104,8 @@ embeddings:
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
|
||||||
CohereEmbeddings:
|
CohereEmbeddings:
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/cohere"
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/cohere"
|
||||||
|
VertexAIEmbeddings:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/google_vertex_ai_palm"
|
||||||
llms:
|
llms:
|
||||||
OpenAI:
|
OpenAI:
|
||||||
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/openai"
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/openai"
|
||||||
|
|
@ -127,8 +129,8 @@ llms:
|
||||||
# There's a bug in this component deactivating until we get it sorted: _language_models.py", line 804, in send_message
|
# 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),
|
# is_blocked=safety_attributes.get("blocked", False),
|
||||||
# AttributeError: 'list' object has no attribute 'get'
|
# AttributeError: 'list' object has no attribute 'get'
|
||||||
# ChatVertexAI:
|
ChatVertexAI:
|
||||||
# documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/google_vertex_ai_palm"
|
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/google_vertex_ai_palm"
|
||||||
###
|
###
|
||||||
memories:
|
memories:
|
||||||
# https://github.com/supabase-community/supabase-py/issues/482
|
# https://github.com/supabase-community/supabase-py/issues/482
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
from contextlib import contextmanager
|
|
||||||
from langflow.settings import settings
|
|
||||||
from sqlmodel import SQLModel, Session, create_engine
|
|
||||||
from langflow.utils.logger import logger
|
|
||||||
|
|
||||||
if settings.database_url and settings.database_url.startswith("sqlite"):
|
|
||||||
connect_args = {"check_same_thread": False}
|
|
||||||
else:
|
|
||||||
connect_args = {}
|
|
||||||
if not settings.database_url:
|
|
||||||
raise RuntimeError("No database_url provided")
|
|
||||||
engine = create_engine(settings.database_url, connect_args=connect_args)
|
|
||||||
|
|
||||||
|
|
||||||
def create_db_and_tables():
|
|
||||||
logger.debug("Creating database and tables")
|
|
||||||
try:
|
|
||||||
SQLModel.metadata.create_all(engine)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"Error creating database and tables: {exc}")
|
|
||||||
raise RuntimeError("Error creating database and tables") from exc
|
|
||||||
# Now check if the table Flow exists, if not, something went wrong
|
|
||||||
# and we need to create the tables again.
|
|
||||||
from sqlalchemy import inspect
|
|
||||||
|
|
||||||
inspector = inspect(engine)
|
|
||||||
if "flow" not in inspector.get_table_names():
|
|
||||||
logger.error("Something went wrong creating the database and tables.")
|
|
||||||
logger.error("Please check your database settings.")
|
|
||||||
|
|
||||||
raise RuntimeError("Something went wrong creating the database and tables.")
|
|
||||||
else:
|
|
||||||
logger.debug("Database and tables created successfully")
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def session_getter():
|
|
||||||
try:
|
|
||||||
session = Session(engine)
|
|
||||||
yield session
|
|
||||||
except Exception as e:
|
|
||||||
print("Session rollback because of exception:", e)
|
|
||||||
session.rollback()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
|
|
||||||
def get_session():
|
|
||||||
with session_getter() as session:
|
|
||||||
yield session
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# Path: src/backend/langflow/database/models/flowstyle.py
|
|
||||||
|
|
||||||
from langflow.database.models.base import SQLModelSerializable
|
|
||||||
from sqlmodel import Field, Relationship
|
|
||||||
from uuid import UUID, uuid4
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from langflow.database.models.flow import Flow
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleBase(SQLModelSerializable):
|
|
||||||
color: str
|
|
||||||
emoji: str
|
|
||||||
flow_id: UUID = Field(default=None, foreign_key="flow.id")
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyle(FlowStyleBase, table=True):
|
|
||||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
|
||||||
flow: "Flow" = Relationship(back_populates="style")
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleUpdate(SQLModelSerializable):
|
|
||||||
color: Optional[str] = None
|
|
||||||
emoji: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleCreate(FlowStyleBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FlowStyleRead(FlowStyleBase):
|
|
||||||
id: UUID
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Dict, Generator, List, Type, Union
|
from typing import Dict, Generator, List, Type, Union
|
||||||
|
|
||||||
from langflow.graph.edge.base import Edge
|
from langflow.graph.edge.base import Edge
|
||||||
from langflow.graph.graph.constants import VERTEX_TYPE_MAP
|
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
from langflow.graph.vertex.types import (
|
from langflow.graph.vertex.types import (
|
||||||
FileToolVertex,
|
FileToolVertex,
|
||||||
|
|
@ -187,10 +187,12 @@ class Graph:
|
||||||
"""Returns the node class based on the node type."""
|
"""Returns the node class based on the node type."""
|
||||||
if node_type in FILE_TOOLS:
|
if node_type in FILE_TOOLS:
|
||||||
return FileToolVertex
|
return FileToolVertex
|
||||||
if node_type in VERTEX_TYPE_MAP:
|
if node_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
|
||||||
return VERTEX_TYPE_MAP[node_type]
|
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_type]
|
||||||
return (
|
return (
|
||||||
VERTEX_TYPE_MAP[node_lc_type] if node_lc_type in VERTEX_TYPE_MAP else Vertex
|
lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_lc_type]
|
||||||
|
if node_lc_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP
|
||||||
|
else Vertex
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_vertices(self) -> List[Vertex]:
|
def _build_vertices(self) -> List[Vertex]:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from langflow.graph.vertex.base import Vertex
|
|
||||||
from langflow.graph.vertex import types
|
from langflow.graph.vertex import types
|
||||||
from langflow.interface.agents.base import agent_creator
|
from langflow.interface.agents.base import agent_creator
|
||||||
from langflow.interface.chains.base import chain_creator
|
from langflow.interface.chains.base import chain_creator
|
||||||
|
|
@ -15,23 +14,45 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from langflow.interface.output_parsers.base import output_parser_creator
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
from langflow.interface.retrievers.base import retriever_creator
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
from langflow.interface.custom.base import custom_component_creator
|
from langflow.interface.custom.base import custom_component_creator
|
||||||
from typing import Dict, Type
|
from langflow.utils.lazy_load import LazyLoadDictBase
|
||||||
|
|
||||||
|
|
||||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
class VertexTypesDict(LazyLoadDictBase):
|
||||||
**{t: types.PromptVertex for t in prompt_creator.to_list()},
|
def __init__(self):
|
||||||
**{t: types.AgentVertex for t in agent_creator.to_list()},
|
self._all_types_dict = None
|
||||||
**{t: types.ChainVertex for t in chain_creator.to_list()},
|
|
||||||
**{t: types.ToolVertex for t in tool_creator.to_list()},
|
@property
|
||||||
**{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
|
def VERTEX_TYPE_MAP(self):
|
||||||
**{t: types.WrapperVertex for t in wrapper_creator.to_list()},
|
return self.all_types_dict
|
||||||
**{t: types.LLMVertex for t in llm_creator.to_list()},
|
|
||||||
**{t: types.MemoryVertex for t in memory_creator.to_list()},
|
def _build_dict(self):
|
||||||
**{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
|
langchain_types_dict = self.get_type_dict()
|
||||||
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
|
return {
|
||||||
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
**langchain_types_dict,
|
||||||
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
"Custom": ["Custom Tool", "Python Function"],
|
||||||
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
}
|
||||||
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
|
|
||||||
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
def get_type_dict(self):
|
||||||
}
|
return {
|
||||||
|
**{t: types.PromptVertex for t in prompt_creator.to_list()},
|
||||||
|
**{t: types.AgentVertex for t in agent_creator.to_list()},
|
||||||
|
**{t: types.ChainVertex for t in chain_creator.to_list()},
|
||||||
|
**{t: types.ToolVertex for t in tool_creator.to_list()},
|
||||||
|
**{t: types.ToolkitVertex for t in toolkits_creator.to_list()},
|
||||||
|
**{t: types.WrapperVertex for t in wrapper_creator.to_list()},
|
||||||
|
**{t: types.LLMVertex for t in llm_creator.to_list()},
|
||||||
|
**{t: types.MemoryVertex for t in memory_creator.to_list()},
|
||||||
|
**{t: types.EmbeddingVertex for t in embedding_creator.to_list()},
|
||||||
|
**{t: types.VectorStoreVertex for t in vectorstore_creator.to_list()},
|
||||||
|
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||||
|
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||||
|
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
||||||
|
**{
|
||||||
|
t: types.CustomComponentVertex
|
||||||
|
for t in custom_component_creator.to_list()
|
||||||
|
},
|
||||||
|
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lazy_load_vertex_dict = VertexTypesDict()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
import ast
|
||||||
from langflow.interface.initialize import loading
|
from langflow.interface.initialize import loading
|
||||||
from langflow.interface.listing import ALL_TYPES_DICT
|
from langflow.interface.listing import lazy_load_dict
|
||||||
from langflow.utils.constants import DIRECT_TYPES
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import sync_to_async
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
@ -61,7 +62,7 @@ class Vertex:
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.base_type is None:
|
if self.base_type is None:
|
||||||
for base_type, value in ALL_TYPES_DICT.items():
|
for base_type, value in lazy_load_dict.ALL_TYPES_DICT.items():
|
||||||
if self.vertex_type in value:
|
if self.vertex_type in value:
|
||||||
self.base_type = base_type
|
self.base_type = base_type
|
||||||
break
|
break
|
||||||
|
|
@ -100,7 +101,9 @@ class Vertex:
|
||||||
params[param_key] = edge.source
|
params[param_key] = edge.source
|
||||||
|
|
||||||
for key, value in template_dict.items():
|
for key, value in template_dict.items():
|
||||||
if key == "_type" or not value.get("show"):
|
# Skip _type and any value that has show == False and is not code
|
||||||
|
# If we don't want to show code but we want to use it
|
||||||
|
if key == "_type" or (not value.get("show") and key != "code"):
|
||||||
continue
|
continue
|
||||||
# If the type is not transformable to a python base class
|
# If the type is not transformable to a python base class
|
||||||
# then we need to get the edge that connects to this node
|
# then we need to get the edge that connects to this node
|
||||||
|
|
@ -112,7 +115,14 @@ class Vertex:
|
||||||
|
|
||||||
params[key] = file_path
|
params[key] = file_path
|
||||||
elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
|
elif value.get("type") in DIRECT_TYPES and params.get(key) is None:
|
||||||
params[key] = value.get("value")
|
if value.get("type") == "code":
|
||||||
|
try:
|
||||||
|
params[key] = ast.literal_eval(value.get("value"))
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug(f"Error parsing code: {exc}")
|
||||||
|
params[key] = value.get("value")
|
||||||
|
else:
|
||||||
|
params[key] = value.get("value")
|
||||||
|
|
||||||
if not value.get("required") and params.get(key) is None:
|
if not value.get("required") and params.get(key) is None:
|
||||||
if value.get("default"):
|
if value.get("default"):
|
||||||
|
|
@ -259,4 +269,8 @@ class Vertex:
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
# Add a message with an emoji, stars for sucess,
|
# Add a message with an emoji, stars for sucess,
|
||||||
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵💫"
|
return (
|
||||||
|
"Built sucessfully ✨"
|
||||||
|
if self._built_object is not None
|
||||||
|
else "Failed to build 😵💫"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,11 @@ class PromptVertex(Vertex):
|
||||||
# so the prompt format doesn't break
|
# so the prompt format doesn't break
|
||||||
artifacts.pop("handle_keys", None)
|
artifacts.pop("handle_keys", None)
|
||||||
try:
|
try:
|
||||||
template = self._built_object.format(**artifacts)
|
template = self._built_object.template
|
||||||
|
for key, value in artifacts.items():
|
||||||
|
if value:
|
||||||
|
replace_key = "{" + key + "}"
|
||||||
|
template = template.replace(replace_key, value)
|
||||||
return (
|
return (
|
||||||
template
|
template
|
||||||
if isinstance(template, str)
|
if isinstance(template, str)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from langchain.agents import types
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.agents import AgentFrontendNode
|
from langflow.template.frontend_node.agents import AgentFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class, build_template_from_method
|
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||||
|
|
@ -53,13 +54,17 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
# Now this is a generator
|
# Now this is a generator
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
for _, agent in self.type_to_loader_dict.items():
|
for _, agent in self.type_to_loader_dict.items():
|
||||||
agent_name = (
|
agent_name = (
|
||||||
agent.function_name()
|
agent.function_name()
|
||||||
if hasattr(agent, "function_name")
|
if hasattr(agent, "function_name")
|
||||||
else agent.__name__
|
else agent.__name__
|
||||||
)
|
)
|
||||||
if agent_name in settings.agents or settings.dev:
|
if (
|
||||||
|
agent_name in settings_manager.settings.AGENTS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
|
):
|
||||||
names.append(agent_name)
|
names.append(agent_name)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,14 @@ from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, List, Optional, Type, Union
|
from typing import Any, Dict, List, Optional, Type, Union
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.agents import AgentExecutor
|
from langchain.agents import AgentExecutor
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.template.base import Template
|
from langflow.template.template.base import Template
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.settings import settings
|
|
||||||
|
|
||||||
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
||||||
|
|
||||||
|
|
@ -26,9 +27,12 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
@property
|
@property
|
||||||
def docs_map(self) -> Dict[str, str]:
|
def docs_map(self) -> Dict[str, str]:
|
||||||
"""A dict with the name of the component as key and the documentation link as value."""
|
"""A dict with the name of the component as key and the documentation link as value."""
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
if self.name_docs_dict is None:
|
if self.name_docs_dict is None:
|
||||||
try:
|
try:
|
||||||
type_settings = getattr(settings, self.type_name)
|
type_settings = getattr(
|
||||||
|
settings_manager.settings, self.type_name.upper()
|
||||||
|
)
|
||||||
self.name_docs_dict = {
|
self.name_docs_dict = {
|
||||||
name: value_dict["documentation"]
|
name: value_dict["documentation"]
|
||||||
for name, value_dict in type_settings.items()
|
for name, value_dict in type_settings.items()
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ from typing import Any, Dict, List, Optional, Type
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.chains import ChainFrontendNode
|
from langflow.template.frontend_node.chains import ChainFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class, build_template_from_method
|
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||||
from langchain import chains
|
from langchain import chains
|
||||||
|
from langchain_experimental.sql import SQLDatabaseChain # type: ignore
|
||||||
|
|
||||||
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
||||||
|
|
||||||
|
|
@ -29,18 +31,22 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict: dict[str, Any] = {
|
self.type_dict: dict[str, Any] = {
|
||||||
chain_name: import_class(f"langchain.chains.{chain_name}")
|
chain_name: import_class(f"langchain.chains.{chain_name}")
|
||||||
for chain_name in chains.__all__
|
for chain_name in chains.__all__
|
||||||
}
|
}
|
||||||
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
||||||
|
|
||||||
|
self.type_dict["SQLDatabaseChain"] = SQLDatabaseChain
|
||||||
|
|
||||||
self.type_dict.update(CUSTOM_CHAINS)
|
self.type_dict.update(CUSTOM_CHAINS)
|
||||||
# Filter according to settings.chains
|
# Filter according to settings.chains
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: chain
|
name: chain
|
||||||
for name, chain in self.type_dict.items()
|
for name, chain in self.type_dict.items()
|
||||||
if name in settings.chains or settings.dev
|
if name in settings_manager.settings.CHAINS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import ast
|
import ast
|
||||||
from typing import Optional
|
from typing import Any, Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
|
@ -63,10 +63,13 @@ class Component(BaseModel):
|
||||||
elif "description" in item_name:
|
elif "description" in item_name:
|
||||||
template_config["description"] = ast.literal_eval(item_value)
|
template_config["description"] = ast.literal_eval(item_value)
|
||||||
|
|
||||||
elif "field_config" in item_name:
|
elif "beta" in item_name:
|
||||||
template_config["field_config"] = ast.literal_eval(item_value)
|
template_config["beta"] = ast.literal_eval(item_value)
|
||||||
|
|
||||||
|
elif "documentation" in item_name:
|
||||||
|
template_config["documentation"] = ast.literal_eval(item_value)
|
||||||
|
|
||||||
return template_config
|
return template_config
|
||||||
|
|
||||||
def build(self):
|
def build(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from langchain.schema import BaseRetriever, Document
|
||||||
from langchain.text_splitter import TextSplitter
|
from langchain.text_splitter import TextSplitter
|
||||||
from langchain.tools import Tool
|
from langchain.tools import Tool
|
||||||
from langchain.vectorstores.base import VectorStore
|
from langchain.vectorstores.base import VectorStore
|
||||||
|
from langchain.schema import BaseOutputParser
|
||||||
|
|
||||||
|
|
||||||
LANGCHAIN_BASE_TYPES = {
|
LANGCHAIN_BASE_TYPES = {
|
||||||
|
|
@ -20,6 +21,7 @@ LANGCHAIN_BASE_TYPES = {
|
||||||
"VectorStore": VectorStore,
|
"VectorStore": VectorStore,
|
||||||
"Embeddings": Embeddings,
|
"Embeddings": Embeddings,
|
||||||
"BaseRetriever": BaseRetriever,
|
"BaseRetriever": BaseRetriever,
|
||||||
|
"BaseOutputParser": BaseOutputParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Langchain base types plus Python base types
|
# Langchain base types plus Python base types
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ from fastapi import HTTPException
|
||||||
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||||
from langflow.interface.custom.component import Component
|
from langflow.interface.custom.component import Component
|
||||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||||
|
from langflow.services.utils import get_db_manager
|
||||||
|
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
|
|
||||||
from langflow.database.base import session_getter
|
from langflow.services.database.utils import session_getter
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.services.database.models.flow import Flow
|
||||||
from pydantic import Extra
|
from pydantic import Extra
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
class CustomComponent(Component, extra=Extra.allow):
|
class CustomComponent(Component, extra=Extra.allow):
|
||||||
|
|
@ -24,6 +26,10 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
|
|
||||||
def custom_repr(self):
|
def custom_repr(self):
|
||||||
|
if isinstance(self.repr_value, dict):
|
||||||
|
return yaml.dump(self.repr_value)
|
||||||
|
if isinstance(self.repr_value, str):
|
||||||
|
return self.repr_value
|
||||||
return str(self.repr_value)
|
return str(self.repr_value)
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
|
|
@ -44,7 +50,9 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
reader = DirectoryReader("", False)
|
reader = DirectoryReader("", False)
|
||||||
|
|
||||||
for type_hint in TYPE_HINT_LIST:
|
for type_hint in TYPE_HINT_LIST:
|
||||||
if reader.is_type_hint_used_but_not_imported(type_hint, code):
|
if reader._is_type_hint_used_in_args(
|
||||||
|
"Optional", code
|
||||||
|
) and not reader._is_type_hint_imported("Optional", code):
|
||||||
error_detail = {
|
error_detail = {
|
||||||
"error": "Type hint Error",
|
"error": "Type hint Error",
|
||||||
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
||||||
|
|
@ -87,9 +95,9 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
return build_method["args"]
|
return build_method["args"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_function_entrypoint_return_type(self) -> str:
|
def get_function_entrypoint_return_type(self) -> List[str]:
|
||||||
if not self.code:
|
if not self.code:
|
||||||
return ""
|
return []
|
||||||
tree = self.get_code_tree(self.code)
|
tree = self.get_code_tree(self.code)
|
||||||
|
|
||||||
component_classes = [
|
component_classes = [
|
||||||
|
|
@ -98,7 +106,7 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
if self.code_class_base_inheritance in cls["bases"]
|
if self.code_class_base_inheritance in cls["bases"]
|
||||||
]
|
]
|
||||||
if not component_classes:
|
if not component_classes:
|
||||||
return ""
|
return []
|
||||||
|
|
||||||
# Assume the first Component class is the one we're interested in
|
# Assume the first Component class is the one we're interested in
|
||||||
component_class = component_classes[0]
|
component_class = component_classes[0]
|
||||||
|
|
@ -109,11 +117,21 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
]
|
]
|
||||||
|
|
||||||
if not build_methods:
|
if not build_methods:
|
||||||
return ""
|
return []
|
||||||
|
|
||||||
build_method = build_methods[0]
|
build_method = build_methods[0]
|
||||||
|
return_type = build_method["return_type"]
|
||||||
|
if not return_type:
|
||||||
|
return []
|
||||||
|
# If the return type is not a Union, then we just return it as a list
|
||||||
|
if "Union" not in return_type:
|
||||||
|
return [return_type] if return_type in self.return_type_valid_list else []
|
||||||
|
|
||||||
return build_method["return_type"]
|
# If the return type is a Union, then we need to parse it
|
||||||
|
return_type = return_type.replace("Union", "").replace("[", "").replace("]", "")
|
||||||
|
return_type = return_type.split(",")
|
||||||
|
return_type = [item.strip() for item in return_type]
|
||||||
|
return [item for item in return_type if item in self.return_type_valid_list]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_main_class_name(self):
|
def get_main_class_name(self):
|
||||||
|
|
@ -154,7 +172,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
from langflow.processing.process import build_sorted_vertices_with_caching
|
from langflow.processing.process import build_sorted_vertices_with_caching
|
||||||
from langflow.processing.process import process_tweaks
|
from langflow.processing.process import process_tweaks
|
||||||
|
|
||||||
with session_getter() as session:
|
db_manager = get_db_manager()
|
||||||
|
with session_getter(db_manager) as session:
|
||||||
graph_data = flow.data if (flow := session.get(Flow, flow_id)) else None
|
graph_data = flow.data if (flow := session.get(Flow, flow_id)) else None
|
||||||
if not graph_data:
|
if not graph_data:
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
|
|
@ -164,7 +183,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
|
|
||||||
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
||||||
get_session = get_session or session_getter
|
get_session = get_session or session_getter
|
||||||
with get_session() as session:
|
db_manager = get_db_manager()
|
||||||
|
with get_session(db_manager) as session:
|
||||||
flows = session.query(Flow).all()
|
flows = session.query(Flow).all()
|
||||||
return flows
|
return flows
|
||||||
|
|
||||||
|
|
@ -177,8 +197,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
get_session: Optional[Callable] = None,
|
get_session: Optional[Callable] = None,
|
||||||
) -> Flow:
|
) -> Flow:
|
||||||
get_session = get_session or session_getter
|
get_session = get_session or session_getter
|
||||||
|
db_manager = get_db_manager()
|
||||||
with get_session() as session:
|
with get_session(db_manager) as session:
|
||||||
if flow_id:
|
if flow_id:
|
||||||
flow = session.query(Flow).get(flow_id)
|
flow = session.query(Flow).get(flow_id)
|
||||||
elif flow_name:
|
elif flow_name:
|
||||||
|
|
@ -190,5 +210,5 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
raise ValueError(f"Flow {flow_name or flow_id} not found")
|
raise ValueError(f"Flow {flow_name or flow_id} not found")
|
||||||
return self.load_flow(flow.id, tweaks)
|
return self.load_flow(flow.id, tweaks)
|
||||||
|
|
||||||
def build(self):
|
def build(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import ast
|
import ast
|
||||||
import zlib
|
import zlib
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class CustomComponentPathValueError(ValueError):
|
class CustomComponentPathValueError(ValueError):
|
||||||
|
|
@ -74,8 +75,11 @@ class DirectoryReader:
|
||||||
}
|
}
|
||||||
for menu in data["menu"]
|
for menu in data["menu"]
|
||||||
]
|
]
|
||||||
filtred = [menu for menu in items if menu["components"]]
|
filtered = [menu for menu in items if menu["components"]]
|
||||||
return {"menu": filtred}
|
logger.debug(
|
||||||
|
f'Filtered components {"with errors" if with_errors else ""}: {filtered}'
|
||||||
|
)
|
||||||
|
return {"menu": filtered}
|
||||||
|
|
||||||
def validate_code(self, file_content):
|
def validate_code(self, file_content):
|
||||||
"""
|
"""
|
||||||
|
|
@ -116,7 +120,7 @@ class DirectoryReader:
|
||||||
file_list.extend(
|
file_list.extend(
|
||||||
os.path.join(root, filename)
|
os.path.join(root, filename)
|
||||||
for filename in files
|
for filename in files
|
||||||
if filename.endswith(".py")
|
if filename.endswith(".py") and not filename.startswith("__")
|
||||||
)
|
)
|
||||||
return file_list
|
return file_list
|
||||||
|
|
||||||
|
|
@ -148,15 +152,19 @@ class DirectoryReader:
|
||||||
Check if a specific type hint is used in the
|
Check if a specific type hint is used in the
|
||||||
function definitions within the given code.
|
function definitions within the given code.
|
||||||
"""
|
"""
|
||||||
module = ast.parse(code)
|
try:
|
||||||
|
module = ast.parse(code)
|
||||||
|
|
||||||
for node in ast.walk(module):
|
for node in ast.walk(module):
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
for arg in node.args.args:
|
for arg in node.args.args:
|
||||||
if self._is_type_hint_in_arg_annotation(
|
if self._is_type_hint_in_arg_annotation(
|
||||||
arg.annotation, type_hint_name
|
arg.annotation, type_hint_name
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
except SyntaxError:
|
||||||
|
# Returns False if the code is not valid Python
|
||||||
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_type_hint_in_arg_annotation(self, annotation, type_hint_name: str) -> bool:
|
def _is_type_hint_in_arg_annotation(self, annotation, type_hint_name: str) -> bool:
|
||||||
|
|
@ -200,8 +208,13 @@ class DirectoryReader:
|
||||||
return False, "Syntax error"
|
return False, "Syntax error"
|
||||||
elif not self.validate_build(file_content):
|
elif not self.validate_build(file_content):
|
||||||
return False, "Missing build function"
|
return False, "Missing build function"
|
||||||
elif self.is_type_hint_used_but_not_imported("Optional", file_content):
|
elif self._is_type_hint_used_in_args(
|
||||||
return False, "Type hint 'Optional' is used but not imported in the code."
|
"Optional", file_content
|
||||||
|
) and not self._is_type_hint_imported("Optional", file_content):
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
"Type hint 'Optional' is used but not imported in the code.",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if self.compress_code_field:
|
if self.compress_code_field:
|
||||||
file_content = str(StringCompressor(file_content).compress_string())
|
file_content = str(StringCompressor(file_content).compress_string())
|
||||||
|
|
@ -213,27 +226,47 @@ class DirectoryReader:
|
||||||
from the .py files in the directory.
|
from the .py files in the directory.
|
||||||
"""
|
"""
|
||||||
response = {"menu": []}
|
response = {"menu": []}
|
||||||
|
logger.debug(
|
||||||
|
"-------------------- Building component menu list --------------------"
|
||||||
|
)
|
||||||
|
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
menu_name = os.path.basename(os.path.dirname(file_path))
|
menu_name = os.path.basename(os.path.dirname(file_path))
|
||||||
|
logger.debug(f"Menu name: {menu_name}")
|
||||||
filename = os.path.basename(file_path)
|
filename = os.path.basename(file_path)
|
||||||
validation_result, result_content = self.process_file(file_path)
|
validation_result, result_content = self.process_file(file_path)
|
||||||
|
logger.debug(f"Validation result: {validation_result}")
|
||||||
|
|
||||||
menu_result = self.find_menu(response, menu_name) or {
|
menu_result = self.find_menu(response, menu_name) or {
|
||||||
"name": menu_name,
|
"name": menu_name,
|
||||||
"path": os.path.dirname(file_path),
|
"path": os.path.dirname(file_path),
|
||||||
"components": [],
|
"components": [],
|
||||||
}
|
}
|
||||||
|
component_name = filename.split(".")[0]
|
||||||
|
# This is the name of the file which will be displayed in the UI
|
||||||
|
# We need to change it from snake_case to CamelCase
|
||||||
|
|
||||||
|
# first check if it's already CamelCase
|
||||||
|
if "_" in component_name:
|
||||||
|
component_name_camelcase = " ".join(
|
||||||
|
word.title() for word in component_name.split("_")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
component_name_camelcase = component_name
|
||||||
|
|
||||||
component_info = {
|
component_info = {
|
||||||
"name": filename.split(".")[0],
|
"name": "CustomComponent",
|
||||||
|
"output_types": [component_name_camelcase],
|
||||||
"file": filename,
|
"file": filename,
|
||||||
"code": result_content if validation_result else "",
|
"code": result_content if validation_result else "",
|
||||||
"error": "" if validation_result else result_content,
|
"error": "" if validation_result else result_content,
|
||||||
}
|
}
|
||||||
menu_result["components"].append(component_info)
|
menu_result["components"].append(component_info)
|
||||||
|
|
||||||
|
logger.debug(f"Component info: {component_info}")
|
||||||
if menu_result not in response["menu"]:
|
if menu_result not in response["menu"]:
|
||||||
response["menu"].append(menu_result)
|
response["menu"].append(menu_result)
|
||||||
|
logger.debug(
|
||||||
|
"-------------------- Component menu list built --------------------"
|
||||||
|
)
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
|
from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode
|
||||||
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
|
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
@ -30,10 +31,12 @@ class DocumentLoaderCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
documentloader.__name__
|
documentloader.__name__
|
||||||
for documentloader in self.type_to_loader_dict.values()
|
for documentloader in self.type_to_loader_dict.values()
|
||||||
if documentloader.__name__ in settings.documentloaders or settings.dev
|
if documentloader.__name__ in settings_manager.settings.DOCUMENTLOADERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
from langflow.interface.custom_lists import embedding_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
|
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -32,10 +33,12 @@ class EmbeddingCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
embedding.__name__
|
embedding.__name__
|
||||||
for embedding in self.type_to_loader_dict.values()
|
for embedding in self.type_to_loader_dict.values()
|
||||||
if embedding.__name__ in settings.embeddings or settings.dev
|
if embedding.__name__ in settings_manager.settings.EMBEDDINGS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,7 @@ def import_by_type(_type: str, name: str) -> Any:
|
||||||
|
|
||||||
def import_custom_component(custom_component: str) -> CustomComponent:
|
def import_custom_component(custom_component: str) -> CustomComponent:
|
||||||
"""Import custom component from custom component name"""
|
"""Import custom component from custom component name"""
|
||||||
return import_class(
|
return import_class("langflow.interface.custom.custom_component.CustomComponent")
|
||||||
f"langflow.interface.custom.custom_component.{custom_component}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def import_output_parser(output_parser: str) -> Any:
|
def import_output_parser(output_parser: str) -> Any:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,11 @@ from langchain.agents.agent import AgentExecutor
|
||||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
from langchain.agents.tools import BaseTool
|
from langchain.agents.tools import BaseTool
|
||||||
from langflow.interface.initialize.llm import initialize_vertexai
|
from langflow.interface.initialize.llm import initialize_vertexai
|
||||||
from langflow.interface.initialize.utils import handle_format_kwargs, handle_node_type
|
from langflow.interface.initialize.utils import (
|
||||||
|
handle_format_kwargs,
|
||||||
|
handle_node_type,
|
||||||
|
handle_partial_variables,
|
||||||
|
)
|
||||||
|
|
||||||
from langflow.interface.initialize.vector_store import vecstore_initializer
|
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||||
|
|
||||||
|
|
@ -29,6 +33,7 @@ from langflow.utils import validate
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.vectorstores.base import VectorStore
|
from langchain.vectorstores.base import VectorStore
|
||||||
from langchain.document_loaders.base import BaseLoader
|
from langchain.document_loaders.base import BaseLoader
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
|
|
@ -40,7 +45,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
if hasattr(custom_node, "initialize"):
|
if hasattr(custom_node, "initialize"):
|
||||||
return custom_node.initialize(**params)
|
return custom_node.initialize(**params)
|
||||||
return custom_node(**params)
|
return custom_node(**params)
|
||||||
|
logger.debug(f"Instantiating {node_type} of type {base_type}")
|
||||||
class_object = import_by_type(_type=base_type, name=node_type)
|
class_object = import_by_type(_type=base_type, name=node_type)
|
||||||
return instantiate_based_on_type(class_object, base_type, node_type, params)
|
return instantiate_based_on_type(class_object, base_type, node_type, params)
|
||||||
|
|
||||||
|
|
@ -60,7 +65,12 @@ def convert_kwargs(params):
|
||||||
kwargs_keys = [key for key in params.keys() if "kwargs" in key or "config" in key]
|
kwargs_keys = [key for key in params.keys() if "kwargs" in key or "config" in key]
|
||||||
for key in kwargs_keys:
|
for key in kwargs_keys:
|
||||||
if isinstance(params[key], str):
|
if isinstance(params[key], str):
|
||||||
params[key] = json.loads(params[key])
|
try:
|
||||||
|
params[key] = json.loads(params[key])
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# if the string is not a valid json string, we will
|
||||||
|
# remove the key from the params
|
||||||
|
params.pop(key, None)
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -78,7 +88,7 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
|
||||||
elif base_type == "toolkits":
|
elif base_type == "toolkits":
|
||||||
return instantiate_toolkit(node_type, class_object, params)
|
return instantiate_toolkit(node_type, class_object, params)
|
||||||
elif base_type == "embeddings":
|
elif base_type == "embeddings":
|
||||||
return instantiate_embedding(class_object, params)
|
return instantiate_embedding(node_type, class_object, params)
|
||||||
elif base_type == "vectorstores":
|
elif base_type == "vectorstores":
|
||||||
return instantiate_vectorstore(class_object, params)
|
return instantiate_vectorstore(class_object, params)
|
||||||
elif base_type == "documentloaders":
|
elif base_type == "documentloaders":
|
||||||
|
|
@ -106,9 +116,12 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_custom_component(node_type, class_object, params):
|
def instantiate_custom_component(node_type, class_object, params):
|
||||||
class_object = get_function_custom(params.pop("code"))
|
# we need to make a copy of the params because we will be
|
||||||
|
# modifying it
|
||||||
|
params_copy = params.copy()
|
||||||
|
class_object = get_function_custom(params_copy.pop("code"))
|
||||||
custom_component = class_object()
|
custom_component = class_object()
|
||||||
built_object = custom_component.build(**params)
|
built_object = custom_component.build(**params_copy)
|
||||||
return built_object, {"repr": custom_component.custom_repr()}
|
return built_object, {"repr": custom_component.custom_repr()}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -134,7 +147,7 @@ def instantiate_llm(node_type, class_object, params: Dict):
|
||||||
# This is a workaround so JinaChat works until streaming is implemented
|
# This is a workaround so JinaChat works until streaming is implemented
|
||||||
# if "openai_api_base" in params and "jina" in params["openai_api_base"]:
|
# if "openai_api_base" in params and "jina" in params["openai_api_base"]:
|
||||||
# False if condition is True
|
# False if condition is True
|
||||||
if node_type == "VertexAI":
|
if "VertexAI" in node_type:
|
||||||
return initialize_vertexai(class_object=class_object, params=params)
|
return initialize_vertexai(class_object=class_object, params=params)
|
||||||
# max_tokens sometimes is a string and should be an int
|
# max_tokens sometimes is a string and should be an int
|
||||||
if "max_tokens" in params:
|
if "max_tokens" in params:
|
||||||
|
|
@ -212,6 +225,9 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
|
||||||
def instantiate_prompt(node_type, class_object, params: Dict):
|
def instantiate_prompt(node_type, class_object, params: Dict):
|
||||||
params, prompt = handle_node_type(node_type, class_object, params)
|
params, prompt = handle_node_type(node_type, class_object, params)
|
||||||
format_kwargs = handle_format_kwargs(prompt, params)
|
format_kwargs = handle_format_kwargs(prompt, params)
|
||||||
|
# Now we'll use partial_format to format the prompt
|
||||||
|
if format_kwargs:
|
||||||
|
prompt = handle_partial_variables(prompt, format_kwargs)
|
||||||
return prompt, format_kwargs
|
return prompt, format_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -245,9 +261,13 @@ def instantiate_toolkit(node_type, class_object: Type[BaseToolkit], params: Dict
|
||||||
return loaded_toolkit
|
return loaded_toolkit
|
||||||
|
|
||||||
|
|
||||||
def instantiate_embedding(class_object, params: Dict):
|
def instantiate_embedding(node_type, class_object, params: Dict):
|
||||||
params.pop("model", None)
|
params.pop("model", None)
|
||||||
params.pop("headers", None)
|
params.pop("headers", None)
|
||||||
|
|
||||||
|
if "VertexAI" in node_type:
|
||||||
|
return initialize_vertexai(class_object=class_object, params=params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,16 @@ def handle_format_kwargs(prompt, params: Dict):
|
||||||
return format_kwargs
|
return format_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def handle_partial_variables(prompt, format_kwargs: Dict):
|
||||||
|
partial_variables = format_kwargs.copy()
|
||||||
|
partial_variables = {
|
||||||
|
key: value for key, value in partial_variables.items() if value
|
||||||
|
}
|
||||||
|
# Remove handle_keys otherwise LangChain raises an error
|
||||||
|
partial_variables.pop("handle_keys", None)
|
||||||
|
return prompt.partial(**partial_variables)
|
||||||
|
|
||||||
|
|
||||||
def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
|
def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
|
||||||
variable = params[input_variable]
|
variable = params[input_variable]
|
||||||
if isinstance(variable, str):
|
if isinstance(variable, str):
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,8 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
|
|
||||||
import pinecone # type: ignore
|
import pinecone # type: ignore
|
||||||
|
|
||||||
pinecone_api_key = params.get("pinecone_api_key")
|
pinecone_api_key = params.pop("pinecone_api_key")
|
||||||
pinecone_env = params.get("pinecone_env")
|
pinecone_env = params.pop("pinecone_env")
|
||||||
|
|
||||||
if pinecone_api_key is None or pinecone_env is None:
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
if os.getenv("PINECONE_API_KEY") is not None:
|
if os.getenv("PINECONE_API_KEY") is not None:
|
||||||
|
|
@ -170,6 +170,26 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
|
|
||||||
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
"""Initialize a ChromaDB object from the params"""
|
"""Initialize a ChromaDB object from the params"""
|
||||||
|
if ( # type: ignore
|
||||||
|
"chroma_server_host" in params or "chroma_server_http_port" in params
|
||||||
|
):
|
||||||
|
import chromadb # type: ignore
|
||||||
|
|
||||||
|
settings_params = {
|
||||||
|
key: params[key]
|
||||||
|
for key, value_ in params.items()
|
||||||
|
if key.startswith("chroma_server_") and value_
|
||||||
|
}
|
||||||
|
chroma_settings = chromadb.config.Settings(**settings_params)
|
||||||
|
params["client_settings"] = chroma_settings
|
||||||
|
else:
|
||||||
|
# remove all chroma_server_ keys from params
|
||||||
|
params = {
|
||||||
|
key: value
|
||||||
|
for key, value in params.items()
|
||||||
|
if not key.startswith("chroma_server_")
|
||||||
|
}
|
||||||
|
|
||||||
persist = params.pop("persist", False)
|
persist = params.pop("persist", False)
|
||||||
if not docs_in_params(params):
|
if not docs_in_params(params):
|
||||||
params.pop("documents", None)
|
params.pop("documents", None)
|
||||||
|
|
|
||||||
|
|
@ -14,34 +14,43 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from langflow.interface.output_parsers.base import output_parser_creator
|
from langflow.interface.output_parsers.base import output_parser_creator
|
||||||
from langflow.interface.retrievers.base import retriever_creator
|
from langflow.interface.retrievers.base import retriever_creator
|
||||||
from langflow.interface.custom.base import custom_component_creator
|
from langflow.interface.custom.base import custom_component_creator
|
||||||
|
from langflow.utils.lazy_load import LazyLoadDictBase
|
||||||
|
|
||||||
|
|
||||||
def get_type_dict():
|
class AllTypesDict(LazyLoadDictBase):
|
||||||
return {
|
def __init__(self):
|
||||||
"agents": agent_creator.to_list(),
|
self._all_types_dict = None
|
||||||
"prompts": prompt_creator.to_list(),
|
|
||||||
"llms": llm_creator.to_list(),
|
@property
|
||||||
"tools": tool_creator.to_list(),
|
def ALL_TYPES_DICT(self):
|
||||||
"chains": chain_creator.to_list(),
|
return self.all_types_dict
|
||||||
"memory": memory_creator.to_list(),
|
|
||||||
"toolkits": toolkits_creator.to_list(),
|
def _build_dict(self):
|
||||||
"wrappers": wrapper_creator.to_list(),
|
langchain_types_dict = self.get_type_dict()
|
||||||
"documentLoaders": documentloader_creator.to_list(),
|
return {
|
||||||
"vectorStore": vectorstore_creator.to_list(),
|
**langchain_types_dict,
|
||||||
"embeddings": embedding_creator.to_list(),
|
"Custom": ["Custom Tool", "Python Function"],
|
||||||
"textSplitters": textsplitter_creator.to_list(),
|
}
|
||||||
"utilities": utility_creator.to_list(),
|
|
||||||
"outputParsers": output_parser_creator.to_list(),
|
def get_type_dict(self):
|
||||||
"retrievers": retriever_creator.to_list(),
|
return {
|
||||||
"custom_components": custom_component_creator.to_list(),
|
"agents": agent_creator.to_list(),
|
||||||
}
|
"prompts": prompt_creator.to_list(),
|
||||||
|
"llms": llm_creator.to_list(),
|
||||||
|
"tools": tool_creator.to_list(),
|
||||||
|
"chains": chain_creator.to_list(),
|
||||||
|
"memory": memory_creator.to_list(),
|
||||||
|
"toolkits": toolkits_creator.to_list(),
|
||||||
|
"wrappers": wrapper_creator.to_list(),
|
||||||
|
"documentLoaders": documentloader_creator.to_list(),
|
||||||
|
"vectorStore": vectorstore_creator.to_list(),
|
||||||
|
"embeddings": embedding_creator.to_list(),
|
||||||
|
"textSplitters": textsplitter_creator.to_list(),
|
||||||
|
"utilities": utility_creator.to_list(),
|
||||||
|
"outputParsers": output_parser_creator.to_list(),
|
||||||
|
"retrievers": retriever_creator.to_list(),
|
||||||
|
"custom_components": custom_component_creator.to_list(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
LANGCHAIN_TYPES_DICT = get_type_dict()
|
lazy_load_dict = AllTypesDict()
|
||||||
|
|
||||||
# Now we'll build a dict with Langchain types and ours
|
|
||||||
|
|
||||||
ALL_TYPES_DICT = {
|
|
||||||
**LANGCHAIN_TYPES_DICT,
|
|
||||||
"Custom": ["Custom Tool", "Python Function"],
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
from langflow.interface.custom_lists import llm_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.llms import LLMFrontendNode
|
from langflow.template.frontend_node.llms import LLMFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
@ -33,10 +34,12 @@ class LLMCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
llm.__name__
|
llm.__name__
|
||||||
for llm in self.type_to_loader_dict.values()
|
for llm in self.type_to_loader_dict.values()
|
||||||
if llm.__name__ in settings.llms or settings.dev
|
if llm.__name__ in settings_manager.settings.LLMS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from langflow.template.frontend_node.memories import MemoryFrontendNode
|
from langflow.template.frontend_node.memories import MemoryFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -48,10 +49,12 @@ class MemoryCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
memory.__name__
|
memory.__name__
|
||||||
for memory in self.type_to_loader_dict.values()
|
for memory in self.type_to_loader_dict.values()
|
||||||
if memory.__name__ in settings.memories or settings.dev
|
if memory.__name__ in settings_manager.settings.MEMORIES
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain import output_parsers
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class, build_template_from_method
|
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||||
|
|
@ -23,6 +24,7 @@ class OutputParserCreator(LangChainTypeCreator):
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
output_parser_name: import_class(
|
output_parser_name: import_class(
|
||||||
f"langchain.output_parsers.{output_parser_name}"
|
f"langchain.output_parsers.{output_parser_name}"
|
||||||
|
|
@ -33,7 +35,8 @@ class OutputParserCreator(LangChainTypeCreator):
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: output_parser
|
name: output_parser
|
||||||
for name, output_parser in self.type_dict.items()
|
for name, output_parser in self.type_dict.items()
|
||||||
if name in settings.output_parsers or settings.dev
|
if name in settings_manager.settings.OUTPUT_PARSERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from langchain import prompts
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
@ -20,6 +21,7 @@ class PromptCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
prompt_name: import_class(f"langchain.prompts.{prompt_name}")
|
prompt_name: import_class(f"langchain.prompts.{prompt_name}")
|
||||||
|
|
@ -34,7 +36,8 @@ class PromptCreator(LangChainTypeCreator):
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: prompt
|
name: prompt
|
||||||
for name, prompt in self.type_dict.items()
|
for name, prompt in self.type_dict.items()
|
||||||
if name in settings.prompts or settings.dev
|
if name in settings_manager.settings.PROMPTS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain import retrievers
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_method, build_template_from_class
|
from langflow.utils.util import build_template_from_method, build_template_from_class
|
||||||
|
|
@ -48,10 +49,12 @@ class RetrieverCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
retriever
|
retriever
|
||||||
for retriever in self.type_to_loader_dict.keys()
|
for retriever in self.type_to_loader_dict.keys()
|
||||||
if retriever in settings.retrievers or settings.dev
|
if retriever in settings_manager.settings.RETRIEVERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from langflow.cache.utils import memoize_dict
|
from langflow.services.cache.utils import memoize_dict
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
|
from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode
|
||||||
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
|
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
@ -30,10 +31,12 @@ class TextSplitterCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
textsplitter.__name__
|
textsplitter.__name__
|
||||||
for textsplitter in self.type_to_loader_dict.values()
|
for textsplitter in self.type_to_loader_dict.values()
|
||||||
if textsplitter.__name__ in settings.textsplitters or settings.dev
|
if textsplitter.__name__ in settings_manager.settings.TEXTSPLITTERS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain.agents import agent_toolkits
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class, import_module
|
from langflow.interface.importing.utils import import_class, import_module
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
@ -29,13 +30,15 @@ class ToolkitCreator(LangChainTypeCreator):
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
toolkit_name: import_class(
|
toolkit_name: import_class(
|
||||||
f"langchain.agents.agent_toolkits.{toolkit_name}"
|
f"langchain.agents.agent_toolkits.{toolkit_name}"
|
||||||
)
|
)
|
||||||
# if toolkit_name is not lower case it is a class
|
# if toolkit_name is not lower case it is a class
|
||||||
for toolkit_name in agent_toolkits.__all__
|
for toolkit_name in agent_toolkits.__all__
|
||||||
if not toolkit_name.islower() and toolkit_name in settings.toolkits
|
if not toolkit_name.islower()
|
||||||
|
and toolkit_name in settings_manager.settings.TOOLKITS
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ from langflow.interface.tools.constants import (
|
||||||
OTHER_TOOLS,
|
OTHER_TOOLS,
|
||||||
)
|
)
|
||||||
from langflow.interface.tools.util import get_tool_params
|
from langflow.interface.tools.util import get_tool_params
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.template.base import Template
|
from langflow.template.template.base import Template
|
||||||
from langflow.utils import util
|
from langflow.utils import util
|
||||||
|
|
@ -66,6 +67,7 @@ class ToolCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
if self.tools_dict is None:
|
if self.tools_dict is None:
|
||||||
all_tools = {}
|
all_tools = {}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,10 @@ class ToolCreator(LangChainTypeCreator):
|
||||||
|
|
||||||
tool_name = tool_params.get("name") or tool
|
tool_name = tool_params.get("name") or tool
|
||||||
|
|
||||||
if tool_name in settings.tools or settings.dev:
|
if (
|
||||||
|
tool_name in settings_manager.settings.TOOLS
|
||||||
|
or settings_manager.settings.DEV
|
||||||
|
):
|
||||||
if tool_name == "JsonSpec":
|
if tool_name == "JsonSpec":
|
||||||
tool_params["path"] = tool_params.pop("dict_") # type: ignore
|
tool_params["path"] = tool_params.pop("dict_") # type: ignore
|
||||||
all_tools[tool_name] = {
|
all_tools[tool_name] = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
from typing import Any
|
import ast
|
||||||
|
import contextlib
|
||||||
|
from typing import Any, List
|
||||||
|
from langflow.api.utils import merge_nested_dicts_with_renaming
|
||||||
from langflow.interface.agents.base import agent_creator
|
from langflow.interface.agents.base import agent_creator
|
||||||
from langflow.interface.chains.base import chain_creator
|
from langflow.interface.chains.base import chain_creator
|
||||||
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||||
|
|
@ -28,7 +31,6 @@ from langflow.interface.retrievers.base import retriever_creator
|
||||||
from langflow.interface.custom.directory_reader import DirectoryReader
|
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import get_base_classes
|
from langflow.utils.util import get_base_classes
|
||||||
from langflow.api.utils import merge_nested_dicts
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
|
@ -145,7 +147,7 @@ def add_code_field(template, raw_code, field_config):
|
||||||
"dynamic": True,
|
"dynamic": True,
|
||||||
"required": True,
|
"required": True,
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"show": True,
|
"show": field_config.pop("show", True),
|
||||||
"multiline": True,
|
"multiline": True,
|
||||||
"value": raw_code,
|
"value": raw_code,
|
||||||
"password": False,
|
"password": False,
|
||||||
|
|
@ -186,7 +188,7 @@ def build_frontend_node(custom_component: CustomComponent):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def update_display_name_and_description(frontend_node, template_config):
|
def update_attributes(frontend_node, template_config):
|
||||||
"""Update the display name and description of a frontend node"""
|
"""Update the display name and description of a frontend node"""
|
||||||
if "display_name" in template_config:
|
if "display_name" in template_config:
|
||||||
frontend_node["display_name"] = template_config["display_name"]
|
frontend_node["display_name"] = template_config["display_name"]
|
||||||
|
|
@ -194,6 +196,12 @@ def update_display_name_and_description(frontend_node, template_config):
|
||||||
if "description" in template_config:
|
if "description" in template_config:
|
||||||
frontend_node["description"] = template_config["description"]
|
frontend_node["description"] = template_config["description"]
|
||||||
|
|
||||||
|
if "beta" in template_config:
|
||||||
|
frontend_node["beta"] = template_config["beta"]
|
||||||
|
|
||||||
|
if "documentation" in template_config:
|
||||||
|
frontend_node["documentation"] = template_config["documentation"]
|
||||||
|
|
||||||
|
|
||||||
def build_field_config(custom_component: CustomComponent):
|
def build_field_config(custom_component: CustomComponent):
|
||||||
"""Build the field configuration for a custom component"""
|
"""Build the field configuration for a custom component"""
|
||||||
|
|
@ -247,55 +255,60 @@ def get_field_properties(extra_field):
|
||||||
if not field_required:
|
if not field_required:
|
||||||
field_type = extract_type_from_optional(field_type)
|
field_type = extract_type_from_optional(field_type)
|
||||||
|
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
field_value = ast.literal_eval(field_value)
|
||||||
return field_name, field_type, field_value, field_required
|
return field_name, field_type, field_value, field_required
|
||||||
|
|
||||||
|
|
||||||
def add_base_classes(frontend_node, return_type):
|
def add_base_classes(frontend_node, return_types: List[str]):
|
||||||
"""Add base classes to the frontend node"""
|
"""Add base classes to the frontend node"""
|
||||||
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
|
for return_type in return_types:
|
||||||
raise HTTPException(
|
if return_type not in CUSTOM_COMPONENT_SUPPORTED_TYPES or return_type is None:
|
||||||
status_code=400,
|
raise HTTPException(
|
||||||
detail={
|
status_code=400,
|
||||||
"error": (
|
detail={
|
||||||
"Invalid return type should be one of: "
|
"error": (
|
||||||
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
"Invalid return type should be one of: "
|
||||||
),
|
f"{list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())}"
|
||||||
"traceback": traceback.format_exc(),
|
),
|
||||||
},
|
"traceback": traceback.format_exc(),
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return_type_instance = CUSTOM_COMPONENT_SUPPORTED_TYPES.get(return_type)
|
return_type_instance = CUSTOM_COMPONENT_SUPPORTED_TYPES.get(return_type)
|
||||||
base_classes = get_base_classes(return_type_instance)
|
base_classes = get_base_classes(return_type_instance)
|
||||||
|
|
||||||
for base_class in base_classes:
|
for base_class in base_classes:
|
||||||
if base_class not in CLASSES_TO_REMOVE:
|
if base_class not in CLASSES_TO_REMOVE:
|
||||||
frontend_node.get("base_classes").append(base_class)
|
frontend_node.get("base_classes").append(base_class)
|
||||||
|
|
||||||
|
|
||||||
def build_langchain_template_custom_component(custom_component: CustomComponent):
|
def build_langchain_template_custom_component(custom_component: CustomComponent):
|
||||||
"""Build a custom component template for the langchain"""
|
"""Build a custom component template for the langchain"""
|
||||||
|
logger.debug("Building custom component template")
|
||||||
frontend_node = build_frontend_node(custom_component)
|
frontend_node = build_frontend_node(custom_component)
|
||||||
|
|
||||||
if frontend_node is None:
|
if frontend_node is None:
|
||||||
return None
|
return None
|
||||||
|
logger.debug("Built base frontend node")
|
||||||
template_config = custom_component.build_template_config
|
template_config = custom_component.build_template_config
|
||||||
|
|
||||||
update_display_name_and_description(frontend_node, template_config)
|
update_attributes(frontend_node, template_config)
|
||||||
|
logger.debug("Updated attributes")
|
||||||
field_config = build_field_config(custom_component)
|
field_config = build_field_config(custom_component)
|
||||||
|
logger.debug("Built field config")
|
||||||
add_extra_fields(
|
add_extra_fields(
|
||||||
frontend_node, field_config, custom_component.get_function_entrypoint_args
|
frontend_node, field_config, custom_component.get_function_entrypoint_args
|
||||||
)
|
)
|
||||||
|
logger.debug("Added extra fields")
|
||||||
frontend_node = add_code_field(
|
frontend_node = add_code_field(
|
||||||
frontend_node, custom_component.code, field_config.get("code", {})
|
frontend_node, custom_component.code, field_config.get("code", {})
|
||||||
)
|
)
|
||||||
|
logger.debug("Added code field")
|
||||||
add_base_classes(
|
add_base_classes(
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
frontend_node, custom_component.get_function_entrypoint_return_type
|
||||||
)
|
)
|
||||||
|
logger.debug("Added base classes")
|
||||||
return frontend_node
|
return frontend_node
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -306,7 +319,7 @@ def load_files_from_path(path: str):
|
||||||
return reader.get_files()
|
return reader.get_files()
|
||||||
|
|
||||||
|
|
||||||
def build_and_validate_all_files(reader, file_list):
|
def build_and_validate_all_files(reader: DirectoryReader, file_list):
|
||||||
"""Build and validate all files"""
|
"""Build and validate all files"""
|
||||||
data = reader.build_component_menu_list(file_list)
|
data = reader.build_component_menu_list(file_list)
|
||||||
|
|
||||||
|
|
@ -319,31 +332,53 @@ def build_and_validate_all_files(reader, file_list):
|
||||||
def build_valid_menu(valid_components):
|
def build_valid_menu(valid_components):
|
||||||
"""Build the valid menu"""
|
"""Build the valid menu"""
|
||||||
valid_menu = {}
|
valid_menu = {}
|
||||||
|
logger.debug("------------------- VALID COMPONENTS -------------------")
|
||||||
for menu_item in valid_components["menu"]:
|
for menu_item in valid_components["menu"]:
|
||||||
menu_name = menu_item["name"]
|
menu_name = menu_item["name"]
|
||||||
valid_menu[menu_name] = {}
|
valid_menu[menu_name] = {}
|
||||||
|
|
||||||
for component in menu_item["components"]:
|
for component in menu_item["components"]:
|
||||||
|
logger.debug(f"Building component: {component}")
|
||||||
try:
|
try:
|
||||||
component_name = component["name"]
|
component_name = component["name"]
|
||||||
component_code = component["code"]
|
component_code = component["code"]
|
||||||
|
component_output_types = component["output_types"]
|
||||||
|
|
||||||
component_extractor = CustomComponent(code=component_code)
|
component_extractor = CustomComponent(code=component_code)
|
||||||
component_extractor.is_check_valid()
|
component_extractor.is_check_valid()
|
||||||
|
|
||||||
component_template = build_langchain_template_custom_component(
|
component_template = build_langchain_template_custom_component(
|
||||||
component_extractor
|
component_extractor
|
||||||
)
|
)
|
||||||
|
component_template["output_types"] = component_output_types
|
||||||
|
if len(component_output_types) == 1:
|
||||||
|
component_name = component_output_types[0]
|
||||||
|
else:
|
||||||
|
file_name = component.get("file").split(".")[0]
|
||||||
|
if "_" in file_name:
|
||||||
|
# turn .py file into camelcase
|
||||||
|
component_name = "".join(
|
||||||
|
[word.capitalize() for word in file_name.split("_")]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
component_name = file_name
|
||||||
|
|
||||||
valid_menu[menu_name][component_name] = component_template
|
valid_menu[menu_name][component_name] = component_template
|
||||||
|
logger.debug(f"Added {component_name} to valid menu to {menu_name}")
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error while building custom component: {exc}")
|
logger.error(f"Error loading Component: {component['output_types']}")
|
||||||
|
logger.exception(
|
||||||
|
f"Error while building custom component {component_output_types}: {exc}"
|
||||||
|
)
|
||||||
|
|
||||||
return valid_menu
|
return valid_menu
|
||||||
|
|
||||||
|
|
||||||
def build_invalid_menu(invalid_components):
|
def build_invalid_menu(invalid_components):
|
||||||
"""Build the invalid menu"""
|
"""Build the invalid menu"""
|
||||||
|
if invalid_components.get("menu"):
|
||||||
|
logger.debug("------------------- INVALID COMPONENTS -------------------")
|
||||||
invalid_menu = {}
|
invalid_menu = {}
|
||||||
for menu_item in invalid_components["menu"]:
|
for menu_item in invalid_components["menu"]:
|
||||||
menu_name = menu_item["name"]
|
menu_name = menu_item["name"]
|
||||||
|
|
@ -364,12 +399,16 @@ def build_invalid_menu(invalid_components):
|
||||||
)
|
)
|
||||||
|
|
||||||
component_template["error"] = component.get("error", None)
|
component_template["error"] = component.get("error", None)
|
||||||
|
logger.debug(component)
|
||||||
|
logger.debug(f"Component Path: {component.get('path', None)}")
|
||||||
|
logger.debug(f"Component Error: {component.get('error', None)}")
|
||||||
component_template.get("template").get("code")["value"] = component_code
|
component_template.get("template").get("code")["value"] = component_code
|
||||||
|
|
||||||
invalid_menu[menu_name][component_name] = component_template
|
invalid_menu[menu_name][component_name] = component_template
|
||||||
|
logger.debug(f"Added {component_name} to invalid menu to {menu_name}")
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(
|
logger.exception(
|
||||||
f"Error while creating custom component [{component_name}]: {str(exc)}"
|
f"Error while creating custom component [{component_name}]: {str(exc)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -388,4 +427,4 @@ def build_langchain_custom_component_list_from_path(path: str):
|
||||||
valid_menu = build_valid_menu(valid_components)
|
valid_menu = build_valid_menu(valid_components)
|
||||||
invalid_menu = build_invalid_menu(invalid_components)
|
invalid_menu = build_invalid_menu(invalid_components)
|
||||||
|
|
||||||
return merge_nested_dicts(valid_menu, invalid_menu)
|
return merge_nested_dicts_with_renaming(valid_menu, invalid_menu)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ from langchain import SQLDatabase, utilities
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
|
from langflow.template.frontend_node.utilities import UtilitiesFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
@ -26,6 +27,7 @@ class UtilityCreator(LangChainTypeCreator):
|
||||||
from the langchain.chains module and filtering them according to the settings.utilities list.
|
from the langchain.chains module and filtering them according to the settings.utilities list.
|
||||||
"""
|
"""
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
utility_name: import_class(f"langchain.utilities.{utility_name}")
|
utility_name: import_class(f"langchain.utilities.{utility_name}")
|
||||||
for utility_name in utilities.__all__
|
for utility_name in utilities.__all__
|
||||||
|
|
@ -35,7 +37,8 @@ class UtilityCreator(LangChainTypeCreator):
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: utility
|
name: utility
|
||||||
for name, utility in self.type_dict.items()
|
for name, utility in self.type_dict.items()
|
||||||
if name in settings.utilities or settings.dev
|
if name in settings_manager.settings.UTILITIES
|
||||||
|
or settings_manager.settings.DEV
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import yaml
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.chat.config import ChatConfig
|
from langflow.services.chat.config import ChatConfig
|
||||||
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
|
|
||||||
def load_file_into_dict(file_path: str) -> dict:
|
def load_file_into_dict(file_path: str) -> dict:
|
||||||
|
|
@ -63,24 +64,21 @@ def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||||
|
|
||||||
def setup_llm_caching():
|
def setup_llm_caching():
|
||||||
"""Setup LLM caching."""
|
"""Setup LLM caching."""
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
from langflow.settings import settings
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
set_langchain_cache(settings)
|
set_langchain_cache(settings_manager.settings)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning(f"Could not import {settings.cache}. ")
|
logger.warning(f"Could not import {settings_manager.settings.CACHE}. ")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"Could not setup LLM caching. Error: {exc}")
|
logger.warning(f"Could not setup LLM caching. Error: {exc}")
|
||||||
|
|
||||||
|
|
||||||
# TODO Rename this here and in `setup_llm_caching`
|
|
||||||
def set_langchain_cache(settings):
|
def set_langchain_cache(settings):
|
||||||
import langchain
|
import langchain
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
|
|
||||||
cache_type = os.getenv("LANGFLOW_LANGCHAIN_CACHE")
|
cache_type = os.getenv("LANGFLOW_LANGCHAIN_CACHE")
|
||||||
cache_class = import_class(f"langchain.cache.{cache_type or settings.cache}")
|
cache_class = import_class(f"langchain.cache.{cache_type or settings.CACHE}")
|
||||||
|
|
||||||
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
||||||
langchain.llm_cache = cache_class()
|
langchain.llm_cache = cache_class()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ from langchain import vectorstores
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.importing.utils import import_class
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.services.utils import get_settings_manager
|
||||||
|
|
||||||
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
|
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import build_template_from_method
|
from langflow.utils.util import build_template_from_method
|
||||||
|
|
@ -43,10 +44,12 @@ class VectorstoreCreator(LangChainTypeCreator):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
|
settings_manager = get_settings_manager()
|
||||||
return [
|
return [
|
||||||
vectorstore
|
vectorstore
|
||||||
for vectorstore in self.type_to_loader_dict.keys()
|
for vectorstore in self.type_to_loader_dict.keys()
|
||||||
if vectorstore in settings.vectorstores or settings.dev
|
if vectorstore in settings_manager.settings.VECTORSTORES
|
||||||
|
or settings_manager.settings.DEV
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,15 @@ from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
from langflow.database.base import create_db_and_tables
|
|
||||||
from langflow.interface.utils import setup_llm_caching
|
from langflow.interface.utils import setup_llm_caching
|
||||||
|
from langflow.services.database.utils import initialize_database
|
||||||
|
from langflow.services.manager import initialize_services
|
||||||
from langflow.utils.logger import configure
|
from langflow.utils.logger import configure
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
"""Create the FastAPI app and include the router."""
|
"""Create the FastAPI app and include the router."""
|
||||||
|
|
||||||
configure()
|
configure()
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
@ -32,9 +34,10 @@ def create_app():
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.on_event("startup")(create_db_and_tables)
|
|
||||||
|
app.on_event("startup")(initialize_services)
|
||||||
|
app.on_event("startup")(initialize_database)
|
||||||
app.on_event("startup")(setup_llm_caching)
|
app.on_event("startup")(setup_llm_caching)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
@ -67,16 +70,19 @@ def get_static_files_dir():
|
||||||
return frontend_path / "frontend"
|
return frontend_path / "frontend"
|
||||||
|
|
||||||
|
|
||||||
def setup_app(static_files_dir: Optional[Path] = None) -> FastAPI:
|
def setup_app(
|
||||||
|
static_files_dir: Optional[Path] = None, backend_only: bool = False
|
||||||
|
) -> FastAPI:
|
||||||
"""Setup the FastAPI app."""
|
"""Setup the FastAPI app."""
|
||||||
# get the directory of the current file
|
# get the directory of the current file
|
||||||
if not static_files_dir:
|
if not static_files_dir:
|
||||||
static_files_dir = get_static_files_dir()
|
static_files_dir = get_static_files_dir()
|
||||||
|
|
||||||
if not static_files_dir or not static_files_dir.exists():
|
if not backend_only and (not static_files_dir or not static_files_dir.exists()):
|
||||||
raise RuntimeError(f"Static files directory {static_files_dir} does not exist.")
|
raise RuntimeError(f"Static files directory {static_files_dir} does not exist.")
|
||||||
app = create_app()
|
app = create_app()
|
||||||
setup_static_files(app, static_files_dir)
|
if not backend_only and static_files_dir is not None:
|
||||||
|
setup_static_files(app, static_files_dir)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
4
src/backend/langflow/services/__init__.py
Normal file
4
src/backend/langflow/services/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .manager import service_manager
|
||||||
|
from .schema import ServiceType
|
||||||
|
|
||||||
|
__all__ = ["service_manager", "ServiceType"]
|
||||||
2
src/backend/langflow/services/base.py
Normal file
2
src/backend/langflow/services/base.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
class Service:
|
||||||
|
name: str
|
||||||
11
src/backend/langflow/services/cache/__init__.py
vendored
Normal file
11
src/backend/langflow/services/cache/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from . import factory, manager
|
||||||
|
from langflow.services.cache.manager import cache_manager
|
||||||
|
from langflow.services.cache.flow import InMemoryCache
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"cache_manager",
|
||||||
|
"factory",
|
||||||
|
"manager",
|
||||||
|
"InMemoryCache",
|
||||||
|
]
|
||||||
11
src/backend/langflow/services/cache/factory.py
vendored
Normal file
11
src/backend/langflow/services/cache/factory.py
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from langflow.services.cache.manager import CacheManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class CacheManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(CacheManager)
|
||||||
|
|
||||||
|
def create(self, settings_service):
|
||||||
|
# Here you would have logic to create and configure a CacheManager
|
||||||
|
return CacheManager()
|
||||||
|
|
@ -2,7 +2,7 @@ import threading
|
||||||
import time
|
import time
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from langflow.cache.base import BaseCache
|
from langflow.services.cache.base import BaseCache
|
||||||
|
|
||||||
|
|
||||||
class InMemoryCache(BaseCache):
|
class InMemoryCache(BaseCache):
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import Any, Awaitable, Callable, List, Optional
|
from typing import Any, Awaitable, Callable, List, Optional
|
||||||
|
from langflow.services.base import Service
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
@ -49,9 +50,11 @@ class AsyncSubject:
|
||||||
await observer()
|
await observer()
|
||||||
|
|
||||||
|
|
||||||
class CacheManager(Subject):
|
class CacheManager(Subject, Service):
|
||||||
"""Manages cache for different clients and notifies observers on changes."""
|
"""Manages cache for different clients and notifies observers on changes."""
|
||||||
|
|
||||||
|
name = "cache_manager"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
0
src/backend/langflow/services/chat/__init__.py
Normal file
0
src/backend/langflow/services/chat/__init__.py
Normal file
11
src/backend/langflow/services/chat/factory.py
Normal file
11
src/backend/langflow/services/chat/factory.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from langflow.services.chat.manager import ChatManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class ChatManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(ChatManager)
|
||||||
|
|
||||||
|
def create(self, settings_service):
|
||||||
|
# Here you would have logic to create and configure a ChatManager
|
||||||
|
return ChatManager()
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from fastapi import WebSocket, status
|
from fastapi import WebSocket, status
|
||||||
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
|
from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
|
||||||
from langflow.cache import cache_manager
|
from langflow.services.base import Service
|
||||||
from langflow.cache.manager import Subject
|
from langflow.services import service_manager
|
||||||
from langflow.chat.utils import process_graph
|
from langflow.services.cache.manager import Subject
|
||||||
|
from langflow.services.chat.utils import process_graph
|
||||||
from langflow.interface.utils import pil_to_base64
|
from langflow.interface.utils import pil_to_base64
|
||||||
|
from langflow.services.schema import ServiceType
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -12,7 +14,7 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from langflow.cache.flow import InMemoryCache
|
from langflow.services.cache.flow import InMemoryCache
|
||||||
|
|
||||||
|
|
||||||
class ChatHistory(Subject):
|
class ChatHistory(Subject):
|
||||||
|
|
@ -42,11 +44,13 @@ class ChatHistory(Subject):
|
||||||
self.history[client_id] = []
|
self.history[client_id] = []
|
||||||
|
|
||||||
|
|
||||||
class ChatManager:
|
class ChatManager(Service):
|
||||||
|
name = "chat_manager"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.active_connections: Dict[str, WebSocket] = {}
|
self.active_connections: Dict[str, WebSocket] = {}
|
||||||
self.chat_history = ChatHistory()
|
self.chat_history = ChatHistory()
|
||||||
self.cache_manager = cache_manager
|
self.cache_manager = service_manager.get(ServiceType.CACHE_MANAGER)
|
||||||
self.cache_manager.attach(self.update)
|
self.cache_manager.attach(self.update)
|
||||||
self.in_memory_cache = InMemoryCache()
|
self.in_memory_cache = InMemoryCache()
|
||||||
|
|
||||||
|
|
@ -117,7 +121,7 @@ class ChatManager:
|
||||||
self, client_id: str, payload: Dict, langchain_object: Any
|
self, client_id: str, payload: Dict, langchain_object: Any
|
||||||
):
|
):
|
||||||
# Process the graph data and chat message
|
# Process the graph data and chat message
|
||||||
chat_inputs = payload.pop("inputs", "")
|
chat_inputs = payload.pop("inputs", {})
|
||||||
chat_inputs = ChatMessage(message=chat_inputs)
|
chat_inputs = ChatMessage(message=chat_inputs)
|
||||||
self.chat_history.add_message(client_id, chat_inputs)
|
self.chat_history.add_message(client_id, chat_inputs)
|
||||||
|
|
||||||
|
|
@ -21,9 +21,9 @@ async def process_graph(
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
try:
|
try:
|
||||||
if not chat_inputs.message:
|
if chat_inputs.message is None:
|
||||||
logger.debug("No message provided")
|
logger.debug("No message provided")
|
||||||
raise ValueError("No message provided")
|
chat_inputs.message = {}
|
||||||
|
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, intermediate_steps = await get_result_and_steps(
|
result, intermediate_steps = await get_result_and_steps(
|
||||||
0
src/backend/langflow/services/database/__init__.py
Normal file
0
src/backend/langflow/services/database/__init__.py
Normal file
17
src/backend/langflow/services/database/factory.py
Normal file
17
src/backend/langflow/services/database/factory.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from langflow.services.database.manager import DatabaseManager
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.settings.manager import SettingsManager
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManagerFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(DatabaseManager)
|
||||||
|
|
||||||
|
def create(self, settings_service: "SettingsManager"):
|
||||||
|
# Here you would have logic to create and configure a DatabaseManager
|
||||||
|
if not settings_service.settings.DATABASE_URL:
|
||||||
|
raise ValueError("No database URL provided")
|
||||||
|
return DatabaseManager(settings_service.settings.DATABASE_URL)
|
||||||
67
src/backend/langflow/services/database/manager.py
Normal file
67
src/backend/langflow/services/database/manager.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from langflow.services.base import Service
|
||||||
|
from sqlmodel import SQLModel, Session, create_engine
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from alembic.config import Config
|
||||||
|
from alembic import command
|
||||||
|
from langflow.services.database import models # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseManager(Service):
|
||||||
|
name = "database_manager"
|
||||||
|
|
||||||
|
def __init__(self, database_url: str):
|
||||||
|
self.database_url = database_url
|
||||||
|
# This file is in langflow.services.database.manager.py
|
||||||
|
# the ini is in langflow
|
||||||
|
langflow_dir = Path(__file__).parent.parent.parent
|
||||||
|
self.script_location = langflow_dir / "alembic"
|
||||||
|
self.alembic_cfg_path = langflow_dir / "alembic.ini"
|
||||||
|
self.engine = create_engine(database_url)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._session = Session(self.engine)
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
if exc_type is not None: # If an exception has been raised
|
||||||
|
logger.error(
|
||||||
|
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
|
||||||
|
)
|
||||||
|
self._session.rollback()
|
||||||
|
else:
|
||||||
|
self._session.commit()
|
||||||
|
self._session.close()
|
||||||
|
|
||||||
|
def get_session(self):
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
yield session
|
||||||
|
|
||||||
|
def run_migrations(self):
|
||||||
|
logger.info(
|
||||||
|
f"Running DB migrations in {self.script_location} on {self.database_url}"
|
||||||
|
)
|
||||||
|
alembic_cfg = Config()
|
||||||
|
alembic_cfg.set_main_option("script_location", str(self.script_location))
|
||||||
|
alembic_cfg.set_main_option("sqlalchemy.url", self.database_url)
|
||||||
|
command.upgrade(alembic_cfg, "head")
|
||||||
|
|
||||||
|
def create_db_and_tables(self):
|
||||||
|
logger.debug("Creating database and tables")
|
||||||
|
try:
|
||||||
|
SQLModel.metadata.create_all(self.engine)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error creating database and tables: {exc}")
|
||||||
|
raise RuntimeError("Error creating database and tables") from exc
|
||||||
|
|
||||||
|
# Now check if the table "flow" exists, if not, something went wrong
|
||||||
|
# and we need to create the tables again.
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
inspector = inspect(self.engine)
|
||||||
|
if "flow" not in inspector.get_table_names():
|
||||||
|
logger.error("Something went wrong creating the database and tables.")
|
||||||
|
logger.error("Please check your database settings.")
|
||||||
|
raise RuntimeError("Something went wrong creating the database and tables.")
|
||||||
|
else:
|
||||||
|
logger.debug("Database and tables created successfully")
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
from .flow import Flow
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Flow"]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from langflow.database.models.base import SQLModelSerializable, SQLModel
|
from langflow.services.database.models.base import SQLModelSerializable, SQLModel
|
||||||
from sqlmodel import Field
|
from sqlmodel import Field
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
# Path: src/backend/langflow/database/models/flow.py
|
# Path: src/backend/langflow/database/models/flow.py
|
||||||
|
|
||||||
from langflow.database.models.base import SQLModelSerializable
|
from langflow.services.database.models.base import SQLModelSerializable
|
||||||
from pydantic import validator
|
from pydantic import validator
|
||||||
from sqlmodel import Field, Relationship, JSON, Column
|
from sqlmodel import Field, JSON, Column
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
# if TYPE_CHECKING:
|
# if TYPE_CHECKING:
|
||||||
from langflow.database.models.flow_style import FlowStyle, FlowStyleRead
|
|
||||||
|
|
||||||
|
|
||||||
class FlowBase(SQLModelSerializable):
|
class FlowBase(SQLModelSerializable):
|
||||||
|
|
@ -35,11 +34,6 @@ class FlowBase(SQLModelSerializable):
|
||||||
class Flow(FlowBase, table=True):
|
class Flow(FlowBase, table=True):
|
||||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
||||||
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
|
data: Optional[Dict] = Field(default=None, sa_column=Column(JSON))
|
||||||
style: Optional["FlowStyle"] = Relationship(
|
|
||||||
back_populates="flow",
|
|
||||||
# use "uselist=False" to make it a one-to-one relationship
|
|
||||||
sa_relationship_kwargs={"uselist": False},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FlowCreate(FlowBase):
|
class FlowCreate(FlowBase):
|
||||||
|
|
@ -50,10 +44,6 @@ class FlowRead(FlowBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
|
|
||||||
|
|
||||||
class FlowReadWithStyle(FlowRead):
|
|
||||||
style: Optional["FlowStyleRead"] = None
|
|
||||||
|
|
||||||
|
|
||||||
class FlowUpdate(SQLModelSerializable):
|
class FlowUpdate(SQLModelSerializable):
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
47
src/backend/langflow/services/database/utils.py
Normal file
47
src/backend/langflow/services/database/utils.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from alembic.util.exc import CommandError
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.database.manager import DatabaseManager
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_database():
|
||||||
|
logger.debug("Initializing database")
|
||||||
|
from langflow.services import service_manager, ServiceType
|
||||||
|
|
||||||
|
database_manager = service_manager.get(ServiceType.DATABASE_MANAGER)
|
||||||
|
try:
|
||||||
|
database_manager.run_migrations()
|
||||||
|
except CommandError as exc:
|
||||||
|
if "Can't locate revision identified by" not in str(exc):
|
||||||
|
raise exc
|
||||||
|
# This means there's wrong revision in the DB
|
||||||
|
# We need to delete the alembic_version table
|
||||||
|
# and run the migrations again
|
||||||
|
logger.warning(
|
||||||
|
"Wrong revision in DB, deleting alembic_version table and running migrations again"
|
||||||
|
)
|
||||||
|
with session_getter(database_manager) as session:
|
||||||
|
session.execute("DROP TABLE alembic_version")
|
||||||
|
database_manager.run_migrations()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error running migrations: {exc}")
|
||||||
|
raise RuntimeError("Error running migrations") from exc
|
||||||
|
database_manager.create_db_and_tables()
|
||||||
|
logger.debug("Database initialized")
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def session_getter(db_manager: "DatabaseManager"):
|
||||||
|
try:
|
||||||
|
session = Session(db_manager.engine)
|
||||||
|
yield session
|
||||||
|
except Exception as e:
|
||||||
|
print("Session rollback because of exception:", e)
|
||||||
|
session.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
6
src/backend/langflow/services/factory.py
Normal file
6
src/backend/langflow/services/factory.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
class ServiceFactory:
|
||||||
|
def __init__(self, service_class):
|
||||||
|
self.service_class = service_class
|
||||||
|
|
||||||
|
def create(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError
|
||||||
87
src/backend/langflow/services/manager.py
Normal file
87
src/backend/langflow/services/manager.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
from langflow.services.schema import ServiceType
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceManager:
|
||||||
|
"""
|
||||||
|
Manages the creation of different services.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.services = {}
|
||||||
|
self.factories = {}
|
||||||
|
|
||||||
|
def register_factory(self, service_factory: "ServiceFactory"):
|
||||||
|
"""
|
||||||
|
Registers a new factory.
|
||||||
|
"""
|
||||||
|
self.factories[service_factory.service_class.name] = service_factory
|
||||||
|
|
||||||
|
def get(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Get (or create) a service by its name.
|
||||||
|
"""
|
||||||
|
if service_name not in self.services:
|
||||||
|
self._create_service(service_name)
|
||||||
|
|
||||||
|
return self.services[service_name]
|
||||||
|
|
||||||
|
def _create_service(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Create a new service given its name.
|
||||||
|
"""
|
||||||
|
self._validate_service_creation(service_name)
|
||||||
|
|
||||||
|
if service_name == ServiceType.SETTINGS_MANAGER:
|
||||||
|
self.services[service_name] = self.factories[service_name].create()
|
||||||
|
else:
|
||||||
|
settings_service = self.get(ServiceType.SETTINGS_MANAGER)
|
||||||
|
self.services[service_name] = self.factories[service_name].create(
|
||||||
|
settings_service
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_service_creation(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Validate whether the service can be created.
|
||||||
|
"""
|
||||||
|
if service_name not in self.factories:
|
||||||
|
raise ValueError(
|
||||||
|
f"No factory registered for the service class '{service_name.name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
ServiceType.SETTINGS_MANAGER not in self.factories
|
||||||
|
and service_name != ServiceType.SETTINGS_MANAGER
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"Cannot create service '{service_name.name}' before the settings service"
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self, service_name: ServiceType):
|
||||||
|
"""
|
||||||
|
Update a service by its name.
|
||||||
|
"""
|
||||||
|
if service_name in self.services:
|
||||||
|
self.services.pop(service_name, None)
|
||||||
|
self.get(service_name)
|
||||||
|
|
||||||
|
|
||||||
|
service_manager = ServiceManager()
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_services():
|
||||||
|
"""
|
||||||
|
Initialize all the services needed.
|
||||||
|
"""
|
||||||
|
from langflow.services.database import factory as database_factory
|
||||||
|
from langflow.services.cache import factory as cache_factory
|
||||||
|
from langflow.services.chat import factory as chat_factory
|
||||||
|
from langflow.services.settings import factory as settings_factory
|
||||||
|
|
||||||
|
service_manager.register_factory(settings_factory.SettingsManagerFactory())
|
||||||
|
service_manager.register_factory(database_factory.DatabaseManagerFactory())
|
||||||
|
service_manager.register_factory(cache_factory.CacheManagerFactory())
|
||||||
|
service_manager.register_factory(chat_factory.ChatManagerFactory())
|
||||||
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