Merge branch 'feature/store' into bug/undo-copy and fixed edit node modal
This commit is contained in:
commit
807ea46464
295 changed files with 17396 additions and 7418 deletions
|
|
@ -15,7 +15,7 @@
|
||||||
// "forwardPorts": [],
|
// "forwardPorts": [],
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
"postCreateCommand": "make install_frontend && make install_backend",
|
"postCreateCommand": "make setup_devcontainer",
|
||||||
|
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"POETRY_VIRTUALENVS_IN_PROJECT": "true"
|
"POETRY_VIRTUALENVS_IN_PROJECT": "true"
|
||||||
|
|
@ -31,11 +31,13 @@
|
||||||
"sourcery.sourcery",
|
"sourcery.sourcery",
|
||||||
"eamodio.gitlens",
|
"eamodio.gitlens",
|
||||||
"ms-vscode.makefile-tools",
|
"ms-vscode.makefile-tools",
|
||||||
"GitHub.vscode-pull-request-github"
|
"GitHub.vscode-pull-request-github",
|
||||||
|
"Codium.codium",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
// "remoteUser": "root"
|
// "remoteUser": "root"
|
||||||
}
|
}
|
||||||
20
.env.example
20
.env.example
|
|
@ -56,6 +56,14 @@ LANGFLOW_REMOVE_API_KEYS=
|
||||||
# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
|
# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
|
||||||
LANGFLOW_CACHE_TYPE=
|
LANGFLOW_CACHE_TYPE=
|
||||||
|
|
||||||
|
# Auto login
|
||||||
|
# If set to true then a superuser will be logged in automatically
|
||||||
|
# and the login page will be skipped, keeping the
|
||||||
|
# default experience of Langflow
|
||||||
|
# Values: true, false
|
||||||
|
# Example: LANGFLOW_AUTO_LOGIN=true
|
||||||
|
LANGFLOW_AUTO_LOGIN=
|
||||||
|
|
||||||
# Superuser username
|
# Superuser username
|
||||||
# Example: LANGFLOW_SUPERUSER=admin
|
# Example: LANGFLOW_SUPERUSER=admin
|
||||||
LANGFLOW_SUPERUSER=
|
LANGFLOW_SUPERUSER=
|
||||||
|
|
@ -63,3 +71,15 @@ LANGFLOW_SUPERUSER=
|
||||||
# Superuser password
|
# Superuser password
|
||||||
# Example: LANGFLOW_SUPERUSER_PASSWORD=123456
|
# Example: LANGFLOW_SUPERUSER_PASSWORD=123456
|
||||||
LANGFLOW_SUPERUSER_PASSWORD=
|
LANGFLOW_SUPERUSER_PASSWORD=
|
||||||
|
|
||||||
|
# STORE_URL
|
||||||
|
# Example: LANGFLOW_STORE_URL=https://api.langflow.store
|
||||||
|
LANGFLOW_STORE_URL=
|
||||||
|
|
||||||
|
# DOWNLOAD_WEBHOOK_URL
|
||||||
|
#
|
||||||
|
LANGFLOW_DOWNLOAD_WEBHOOK_URL=
|
||||||
|
|
||||||
|
# LIKE_WEBHOOK_URL
|
||||||
|
#
|
||||||
|
LANGFLOW_LIKE_WEBHOOK_URL=
|
||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
|
@ -15,7 +15,7 @@ on:
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
POETRY_VERSION: "1.4.0"
|
POETRY_VERSION: "1.7.0"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -254,4 +254,6 @@ langflow.db
|
||||||
|
|
||||||
/tmp/*
|
/tmp/*
|
||||||
src/backend/langflow/frontend/
|
src/backend/langflow/frontend/
|
||||||
.docker
|
.docker
|
||||||
|
|
||||||
|
.idea
|
||||||
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
|
@ -16,7 +16,8 @@
|
||||||
"debug"
|
"debug"
|
||||||
],
|
],
|
||||||
"jinja": true,
|
"jinja": true,
|
||||||
"justMyCode": true
|
"justMyCode": true,
|
||||||
|
"envFile": "${workspaceFolder}/.env"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Python: Remote Attach",
|
"name": "Python: Remote Attach",
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ WORKDIR $HOME/app
|
||||||
|
|
||||||
COPY --chown=user . $HOME/app
|
COPY --chown=user . $HOME/app
|
||||||
|
|
||||||
RUN pip install langflow>==0.0.86 -U --user
|
RUN pip install langflow>==0.5.0 -U --user
|
||||||
CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
|
CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
|
||||||
|
|
|
||||||
22
Makefile
22
Makefile
|
|
@ -23,16 +23,19 @@ tests:
|
||||||
poetry run pytest tests
|
poetry run pytest tests
|
||||||
|
|
||||||
tests_frontend:
|
tests_frontend:
|
||||||
cd src/frontend && ./run-tests.sh
|
ifeq ($(UI), true)
|
||||||
|
cd src/frontend && ./run-tests.sh --ui
|
||||||
|
else
|
||||||
|
cd src/frontend && ./run-tests.sh
|
||||||
|
endif
|
||||||
|
|
||||||
format:
|
format:
|
||||||
poetry run black .
|
|
||||||
poetry run ruff . --fix
|
poetry run ruff . --fix
|
||||||
|
poetry run ruff format .
|
||||||
cd src/frontend && npm run format
|
cd src/frontend && npm run format
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
poetry run mypy src/backend/langflow
|
poetry run mypy src/backend/langflow
|
||||||
poetry run black . --check
|
|
||||||
poetry run ruff . --fix
|
poetry run ruff . --fix
|
||||||
|
|
||||||
install_frontend:
|
install_frontend:
|
||||||
|
|
@ -42,18 +45,20 @@ install_frontendc:
|
||||||
cd src/frontend && rm -rf node_modules package-lock.json && npm install
|
cd src/frontend && rm -rf node_modules package-lock.json && npm install
|
||||||
|
|
||||||
run_frontend:
|
run_frontend:
|
||||||
|
@-kill -9 `lsof -t -i:3000`
|
||||||
cd src/frontend && npm start
|
cd src/frontend && npm start
|
||||||
|
|
||||||
run_cli:
|
run_cli:
|
||||||
poetry run langflow run --path src/frontend/build
|
poetry run langflow --path src/frontend/build
|
||||||
|
|
||||||
run_cli_debug:
|
run_cli_debug:
|
||||||
poetry run langflow run --path src/frontend/build --log-level debug
|
poetry run langflow --path src/frontend/build --log-level debug
|
||||||
|
|
||||||
setup_devcontainer:
|
setup_devcontainer:
|
||||||
make init
|
make init
|
||||||
make build_frontend
|
make build_frontend
|
||||||
poetry run langflow --path src/frontend/build
|
@echo 'Run Cli'
|
||||||
|
make run_cli
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
@-make install_frontend || (echo "An error occurred while installing frontend dependencies. Attempting to fix." && make install_frontendc)
|
@-make install_frontend || (echo "An error occurred while installing frontend dependencies. Attempting to fix." && make install_frontendc)
|
||||||
|
|
@ -68,12 +73,13 @@ install_backend:
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
make install_backend
|
make install_backend
|
||||||
|
@-kill -9 `lsof -t -i:7860`
|
||||||
ifeq ($(login),1)
|
ifeq ($(login),1)
|
||||||
@echo "Running backend without autologin";
|
@echo "Running backend without autologin";
|
||||||
poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser
|
poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env
|
||||||
else
|
else
|
||||||
@echo "Running backend with autologin";
|
@echo "Running backend with autologin";
|
||||||
LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser
|
LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env
|
||||||
endif
|
endif
|
||||||
|
|
||||||
build_and_run:
|
build_and_run:
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ ENV PYTHONUNBUFFERED=1 \
|
||||||
\
|
\
|
||||||
# poetry
|
# poetry
|
||||||
# https://python-poetry.org/docs/configuration/#using-environment-variables
|
# https://python-poetry.org/docs/configuration/#using-environment-variables
|
||||||
POETRY_VERSION=1.5.1 \
|
POETRY_VERSION=1.7 \
|
||||||
# make poetry install to this location
|
# make poetry install to this location
|
||||||
POETRY_HOME="/opt/poetry" \
|
POETRY_HOME="/opt/poetry" \
|
||||||
# make poetry create the virtual environment in the project's root
|
# make poetry create the virtual environment in the project's root
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
RUN apt-get update && apt-get install gcc g++ git make -y
|
RUN apt-get update && apt-get install gcc g++ git make -y && apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN useradd -m -u 1000 user
|
RUN useradd -m -u 1000 user
|
||||||
USER user
|
USER user
|
||||||
ENV HOME=/home/user \
|
ENV HOME=/home/user \
|
||||||
PATH=/home/user/.local/bin:$PATH
|
PATH=/home/user/.local/bin:$PATH
|
||||||
|
|
||||||
WORKDIR $HOME/app
|
WORKDIR $HOME/app
|
||||||
|
|
||||||
COPY --chown=user . $HOME/app
|
COPY --chown=user . $HOME/app
|
||||||
|
|
||||||
RUN pip install langflow>==0.0.71 -U --user
|
RUN pip install langflow>==0.5.0 -U --user
|
||||||
CMD ["langflow", "--host", "0.0.0.0", "--port", "7860"]
|
CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ services:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "7860:7860"
|
- "7860:7860"
|
||||||
command: langflow --host 0.0.0.0
|
command: langflow run --host 0.0.0.0
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,22 @@ Embeddings are vector representations of text that capture the semantic meaning
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### BedrockEmbeddings
|
||||||
|
|
||||||
|
Used to load [Amazon Bedrocks’s](https://aws.amazon.com/bedrock/) embedding models.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **credentials_profile_name:** The name of the profile in the ~/.aws/credentials or ~/.aws/config files, which has either access keys or role information specified. If not specified, the default credential profile or, if on an EC2 instance, credentials from IMDS will be used. See [the AWS documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html) for more details.
|
||||||
|
|
||||||
|
- **model_id:** Id of the model to call, e.g., amazon.titan-embed-text-v1, this is equivalent to the modelId property in the list-foundation-models api.
|
||||||
|
|
||||||
|
- **endpoint_url:** Needed if you don’t want to default to us-east-1 endpoint.
|
||||||
|
|
||||||
|
- **region_name:** The aws region e.g., us-west-2. Fallsback to AWS_DEFAULT_REGION env variable or region specified in ~/.aws/config in case it is not provided here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### CohereEmbeddings
|
### CohereEmbeddings
|
||||||
|
|
||||||
Used to load [Cohere’s](https://cohere.com/) embedding models.
|
Used to load [Cohere’s](https://cohere.com/) embedding models.
|
||||||
|
|
|
||||||
|
|
@ -217,4 +217,40 @@ Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP).
|
||||||
- **top_k:** How the model selects tokens for output, the next token is selected from – defaults to `40`.
|
- **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`.
|
- **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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### QianfanLLMEndpoint
|
||||||
|
|
||||||
|
Wrapper around [Baidu Qianfan](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) large language models.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
The Qianfan Big Model Platform is a one-stop platform for enterprise developers to develop and operate large models and services. It provides data management based on ERNIE Bot's underlying model (Ernie Bot), automatic model customization and fine-tuning, and one-stop large-scale model customization services for cloud deployment of prediction services, and provides ERNIE Bot's enterprise level service API that can be quickly called, helping to implement the demand for generative AI applications in various industries.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **Model Name:** Model name. you could get from https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu preset models are mapping to an endpoint. `Model Name` will be ignored if `Endpoint` is set.
|
||||||
|
- **Qianfan Ak:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
|
||||||
|
- **Qianfan Sk:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
|
||||||
|
- **Top p:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. The diversity of the output text is affected, and the larger the value, the stronger the diversity of the generated text - defaults to `0.8`.
|
||||||
|
- **Temperature:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. Higher values make the output more random, while lower values make it more concentrated and deterministic - defaults to `0.95`.
|
||||||
|
- **Penalty Score:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. By increasing the penalty for generated tokens, the phenomenon of duplicate generation is reduced. A higher value indicates a higher penalty - defaults to `1.0`.
|
||||||
|
- **Endpoint:** Endpoint of the Qianfan LLM, required if custom model used.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### QianfanChatEndpoint
|
||||||
|
|
||||||
|
Wrapper around [Baidu Qianfan](https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html) chat large language models. This component supports some of the LLMs (Large Language Models) available by Baidu qianfan and is used for tasks such as chatbots, Generative Question-Answering (GQA), and summarization.
|
||||||
|
|
||||||
|
:::info
|
||||||
|
The Qianfan Big Model Platform is a one-stop platform for enterprise developers to develop and operate large models and services. It provides data management based on ERNIE Bot's underlying model (Ernie Bot), automatic model customization and fine-tuning, and one-stop large-scale model customization services for cloud deployment of prediction services, and provides ERNIE Bot's enterprise level service API that can be quickly called, helping to implement the demand for generative AI applications in various industries.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **Model Name:** Model name. you could get from https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu preset models are mapping to an endpoint. `Model Name` will be ignored if `Endpoint` is set.
|
||||||
|
- **Qianfan Ak:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
|
||||||
|
- **Qianfan Sk:** which you could get from https://cloud.baidu.com/product/wenxinworkshop.
|
||||||
|
- **Top p:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. The diversity of the output text is affected, and the larger the value, the stronger the diversity of the generated text - defaults to `0.8`.
|
||||||
|
- **Temperature:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. Higher values make the output more random, while lower values make it more concentrated and deterministic - defaults to `0.95`.
|
||||||
|
- **Penalty Score:** Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo. By increasing the penalty for generated tokens, the phenomenon of duplicate generation is reduced. A higher value indicates a higher penalty - defaults to `1.0`.
|
||||||
|
- **Endpoint:** Endpoint of the Qianfan LLM, required if custom model used.
|
||||||
|
|
@ -5,7 +5,7 @@ import ReactPlayer from "react-player";
|
||||||
|
|
||||||
# Component
|
# Component
|
||||||
|
|
||||||
Components are the building blocks of the flows. They are made of inputs, outputs, and parameters that define their functionality, providing a convenient and straightforward way to compose LLM-based applications. Learn more about components and how they work in the LangChain [documentation](https://docs.langchain.com/docs/category/components) section.
|
Components are the building blocks of the flows. They are made of inputs, outputs, and parameters that define their functionality, providing a convenient and straightforward way to compose LLM-based applications. Learn more about components and how they work in the LangChain [documentation](https://python.langchain.com/docs/integrations/components) section.
|
||||||
|
|
||||||
### Component's Features
|
### Component's Features
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ module.exports = {
|
||||||
[
|
[
|
||||||
remarkCodeHike,
|
remarkCodeHike,
|
||||||
{
|
{
|
||||||
theme: "github-light",
|
theme: "github-dark",
|
||||||
showCopyButton: true,
|
showCopyButton: true,
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
},
|
},
|
||||||
|
|
@ -112,8 +112,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
colorMode: {
|
colorMode: {
|
||||||
defaultMode: "light",
|
defaultMode: "light",
|
||||||
disableSwitch: true,
|
/* Allow users to chose light or dark mode. */
|
||||||
respectPrefersColorScheme: false,
|
disableSwitch: false,
|
||||||
|
/* Respect user preferences, such as low light mode in the evening */
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
},
|
},
|
||||||
announcementBar: {
|
announcementBar: {
|
||||||
content:
|
content:
|
||||||
|
|
|
||||||
932
package-lock.json
generated
Normal file
932
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,932 @@
|
||||||
|
{
|
||||||
|
"name": "langflow",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"cmdk": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.23.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz",
|
||||||
|
"integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==",
|
||||||
|
"dependencies": {
|
||||||
|
"regenerator-runtime": "^0.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
|
||||||
|
"integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.4.2",
|
||||||
|
"@floating-ui/utils": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/react-dom": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/dom": "^1.5.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/primitive": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-arrow": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.0",
|
||||||
|
"@radix-ui/react-context": "1.0.0",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.0.0",
|
||||||
|
"@radix-ui/react-focus-guards": "1.0.0",
|
||||||
|
"@radix-ui/react-focus-scope": "1.0.0",
|
||||||
|
"@radix-ui/react-id": "1.0.0",
|
||||||
|
"@radix-ui/react-portal": "1.0.0",
|
||||||
|
"@radix-ui/react-presence": "1.0.0",
|
||||||
|
"@radix-ui/react-primitive": "1.0.0",
|
||||||
|
"@radix-ui/react-slot": "1.0.0",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||||
|
"aria-hidden": "^1.1.1",
|
||||||
|
"react-remove-scroll": "2.5.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.0",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.0",
|
||||||
|
"@radix-ui/react-primitive": "1.0.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||||
|
"@radix-ui/react-use-escape-keydown": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.0",
|
||||||
|
"@radix-ui/react-primitive": "1.0.0",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-primitive": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.0",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-slot": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
|
||||||
|
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.3",
|
||||||
|
"react-style-singleton": "^2.2.1",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.0",
|
||||||
|
"use-sidecar": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-dismissable-layer": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||||
|
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-focus-guards": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-focus-scope": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-id": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popover": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-context": "1.0.1",
|
||||||
|
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||||
|
"@radix-ui/react-focus-guards": "1.0.1",
|
||||||
|
"@radix-ui/react-focus-scope": "1.0.4",
|
||||||
|
"@radix-ui/react-id": "1.0.1",
|
||||||
|
"@radix-ui/react-popper": "1.1.3",
|
||||||
|
"@radix-ui/react-portal": "1.0.4",
|
||||||
|
"@radix-ui/react-presence": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-slot": "1.0.2",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||||
|
"aria-hidden": "^1.1.1",
|
||||||
|
"react-remove-scroll": "2.5.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-popper": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@floating-ui/react-dom": "^2.0.0",
|
||||||
|
"@radix-ui/react-arrow": "1.0.3",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-context": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1",
|
||||||
|
"@radix-ui/react-use-rect": "1.0.1",
|
||||||
|
"@radix-ui/react-use-size": "1.0.1",
|
||||||
|
"@radix-ui/rect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-portal": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-presence": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-primitive": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-slot": "1.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-slot": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-escape-keydown": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-rect": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/rect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/react-use-size": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@radix-ui/rect": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aria-hidden": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cmdk": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "1.0.0",
|
||||||
|
"command-score": "0.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/command-score": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w=="
|
||||||
|
},
|
||||||
|
"node_modules/detect-node-es": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||||
|
},
|
||||||
|
"node_modules/get-nonce": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/invariant": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-tokens": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
|
},
|
||||||
|
"node_modules/loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"loose-envify": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "18.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
|
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "18.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
|
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"scheduler": "^0.23.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-remove-scroll": {
|
||||||
|
"version": "2.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||||
|
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-remove-scroll-bar": "^2.3.3",
|
||||||
|
"react-style-singleton": "^2.2.1",
|
||||||
|
"tslib": "^2.1.0",
|
||||||
|
"use-callback-ref": "^1.3.0",
|
||||||
|
"use-sidecar": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-remove-scroll-bar": {
|
||||||
|
"version": "2.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz",
|
||||||
|
"integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==",
|
||||||
|
"dependencies": {
|
||||||
|
"react-style-singleton": "^2.2.1",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-style-singleton": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"get-nonce": "^1.0.0",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||||
|
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
||||||
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||||
|
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
|
},
|
||||||
|
"node_modules/use-callback-ref": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/use-sidecar": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-node-es": "^1.1.0",
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package.json
Normal file
6
package.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"cmdk": "^0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
5311
poetry.lock
generated
5311
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.5.1"
|
version = "0.6.0a0"
|
||||||
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 = [
|
||||||
|
|
@ -25,30 +25,32 @@ documentation = "https://docs.langflow.org"
|
||||||
langflow = "langflow.__main__:main"
|
langflow = "langflow.__main__:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
|
|
||||||
|
|
||||||
python = ">=3.9,<3.11"
|
python = ">=3.9,<3.11"
|
||||||
fastapi = "^0.103.0"
|
fastapi = "^0.104.0"
|
||||||
uvicorn = "^0.23.0"
|
uvicorn = "^0.23.0"
|
||||||
beautifulsoup4 = "^4.12.2"
|
beautifulsoup4 = "^4.12.2"
|
||||||
google-search-results = "^2.4.1"
|
google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
gunicorn = "^21.2.0"
|
gunicorn = "^21.2.0"
|
||||||
langchain = "^0.0.308"
|
langchain = "~0.0.338"
|
||||||
openai = "^0.27.8"
|
openai = "^1.3.4"
|
||||||
pandas = "2.0.3"
|
pandas = "2.0.3"
|
||||||
chromadb = "^0.3.21"
|
chromadb = "^0.4.0"
|
||||||
huggingface-hub = { version = "^0.16.0", extras = ["inference"] }
|
huggingface-hub = { version = "^0.19.0", extras = ["inference"] }
|
||||||
rich = "^13.5.0"
|
rich = "^13.7.0"
|
||||||
llama-cpp-python = { version = "~0.1.0", optional = true }
|
llama-cpp-python = { version = "~0.2.0", optional = true }
|
||||||
networkx = "^3.1"
|
networkx = "^3.1"
|
||||||
unstructured = "^0.10.0"
|
unstructured = "^0.10.0"
|
||||||
pypdf = "^3.15.0"
|
pypdf = "^3.17.0"
|
||||||
lxml = "^4.9.2"
|
lxml = "^4.9.2"
|
||||||
pysrt = "^1.1.2"
|
pysrt = "^1.1.2"
|
||||||
fake-useragent = "^1.2.1"
|
fake-useragent = "^1.3.0"
|
||||||
docstring-parser = "^0.15"
|
docstring-parser = "^0.15"
|
||||||
psycopg2-binary = "^2.9.6"
|
psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^12.0.0"
|
pyarrow = "^14.0.0"
|
||||||
tiktoken = "~0.5.0"
|
tiktoken = "~0.5.0"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
qdrant-client = "^1.4.0"
|
qdrant-client = "^1.4.0"
|
||||||
|
|
@ -57,25 +59,26 @@ weaviate-client = "^3.23.0"
|
||||||
jina = "3.15.2"
|
jina = "3.15.2"
|
||||||
sentence-transformers = { version = "^2.2.2", optional = true }
|
sentence-transformers = { version = "^2.2.2", optional = true }
|
||||||
ctransformers = { version = "^0.2.10", optional = true }
|
ctransformers = { version = "^0.2.10", optional = true }
|
||||||
cohere = "^4.27.0"
|
cohere = "^4.32.0"
|
||||||
python-multipart = "^0.0.6"
|
python-multipart = "^0.0.6"
|
||||||
sqlmodel = "^0.0.8"
|
# install sqlmodel using https://github.com/honglei/sqlmodel.git
|
||||||
|
sqlmodel = { git = "https://github.com/honglei/sqlmodel.git", branch = "main" }
|
||||||
faiss-cpu = "^1.7.4"
|
faiss-cpu = "^1.7.4"
|
||||||
anthropic = "^0.3.0"
|
anthropic = "^0.5.0"
|
||||||
orjson = "3.9.3"
|
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"
|
||||||
appdirs = "^1.4.4"
|
platformdirs = "^3.11.0"
|
||||||
pinecone-client = "^2.2.2"
|
pinecone-client = "^2.2.2"
|
||||||
supabase = "^1.0.3"
|
pymongo = "^4.5.0"
|
||||||
pymongo = "^4.4.0"
|
supabase = "^2.0.3"
|
||||||
certifi = "^2023.5.7"
|
certifi = "^2023.5.7"
|
||||||
google-cloud-aiplatform = "^1.26.1"
|
google-cloud-aiplatform = "^1.36.0"
|
||||||
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"
|
langchain-experimental = "*"
|
||||||
celery = { extras = ["redis"], version = "^5.3.1", optional = true }
|
celery = { extras = ["redis"], version = "^5.3.1", optional = true }
|
||||||
redis = { version = "^4.6.0", optional = true }
|
redis = { version = "^4.6.0", optional = true }
|
||||||
flower = { version = "^2.0.0", optional = true }
|
flower = { version = "^2.0.0", optional = true }
|
||||||
|
|
@ -84,28 +87,35 @@ passlib = "^1.7.4"
|
||||||
bcrypt = "^4.0.1"
|
bcrypt = "^4.0.1"
|
||||||
python-jose = "^3.3.0"
|
python-jose = "^3.3.0"
|
||||||
metaphor-python = "^0.1.11"
|
metaphor-python = "^0.1.11"
|
||||||
|
pydantic = "^2.0.0"
|
||||||
|
pydantic-settings = "^2.0.3"
|
||||||
|
zep-python = { version = "^1.3.0", allow-prereleases = true }
|
||||||
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
||||||
loguru = "^0.7.1"
|
loguru = "^0.7.1"
|
||||||
langfuse = "^1.0.13"
|
langfuse = "^1.1.11"
|
||||||
pillow = "^10.0.0"
|
pillow = "^10.0.0"
|
||||||
metal-sdk = "^2.2.0"
|
metal-sdk = "^2.4.0"
|
||||||
markupsafe = "^2.1.3"
|
markupsafe = "^2.1.3"
|
||||||
|
extract-msg = "^0.45.0"
|
||||||
|
jq = "^1.6.0"
|
||||||
|
boto3 = "^1.28.63"
|
||||||
|
numexpr = "^2.8.6"
|
||||||
|
qianfan = "0.0.5"
|
||||||
|
pgvector = "^0.2.3"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
pytest-asyncio = "^0.21.1"
|
||||||
types-redis = "^4.6.0.5"
|
types-redis = "^4.6.0.5"
|
||||||
black = "^23.1.0"
|
|
||||||
ipykernel = "^6.21.2"
|
ipykernel = "^6.21.2"
|
||||||
mypy = "^1.1.1"
|
mypy = "^1.1.1"
|
||||||
ruff = "^0.0.254"
|
ruff = "^0.1.5"
|
||||||
httpx = "*"
|
httpx = "*"
|
||||||
pytest = "^7.2.2"
|
pytest = "^7.4.2"
|
||||||
types-requests = "^2.28.11"
|
types-requests = "^2.31.0"
|
||||||
requests = "^2.28.0"
|
requests = "^2.31.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.1.0"
|
||||||
pandas-stubs = "^2.0.0.230412"
|
pandas-stubs = "^2.0.0.230412"
|
||||||
types-pillow = "^9.5.0.2"
|
types-pillow = "^9.5.0.2"
|
||||||
types-appdirs = "^1.4.3.5"
|
|
||||||
types-pyyaml = "^6.0.12.8"
|
types-pyyaml = "^6.0.12.8"
|
||||||
types-python-jose = "^3.3.4.8"
|
types-python-jose = "^3.3.4.8"
|
||||||
types-passlib = "^1.7.7.13"
|
types-passlib = "^1.7.7.13"
|
||||||
|
|
@ -134,6 +144,7 @@ markers = ["async_test"]
|
||||||
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
exclude = ["src/backend/langflow/alembic/*"]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import typer
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from langflow.main import setup_app
|
from langflow.main import setup_app
|
||||||
from langflow.services.database.utils import session_getter
|
from langflow.services.database.utils import session_getter
|
||||||
from langflow.services.getters import get_db_service, get_settings_service
|
from langflow.services.deps import get_db_service, get_settings_service
|
||||||
from langflow.services.utils import initialize_services, initialize_settings_service
|
from langflow.services.utils import initialize_services, initialize_settings_service
|
||||||
from langflow.utils.logger import configure, logger
|
from langflow.utils.logger import configure, logger
|
||||||
from multiprocess import Process, cpu_count # type: ignore
|
from multiprocess import Process, cpu_count # type: ignore
|
||||||
|
|
@ -61,6 +61,8 @@ def set_var_for_macos_issue():
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
|
os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
|
||||||
|
# https://stackoverflow.com/questions/75747888/uwsgi-segmentation-fault-with-flask-python-app-behind-nginx-after-running-for-2 # noqa
|
||||||
|
os.environ["no_proxy"] = "*" # to avoid error with gunicorn
|
||||||
logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")
|
logger.debug("Set OBJC_DISABLE_INITIALIZE_FORK_SAFETY to YES to avoid error")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -70,6 +72,7 @@ def update_settings(
|
||||||
dev: bool = False,
|
dev: bool = False,
|
||||||
remove_api_keys: bool = False,
|
remove_api_keys: bool = False,
|
||||||
components_path: Optional[Path] = None,
|
components_path: Optional[Path] = None,
|
||||||
|
store: bool = True,
|
||||||
):
|
):
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
|
|
||||||
|
|
@ -88,16 +91,15 @@ def update_settings(
|
||||||
if components_path:
|
if components_path:
|
||||||
logger.debug(f"Adding component path {components_path}")
|
logger.debug(f"Adding component path {components_path}")
|
||||||
settings_service.settings.update_settings(COMPONENTS_PATH=components_path)
|
settings_service.settings.update_settings(COMPONENTS_PATH=components_path)
|
||||||
|
if not store:
|
||||||
|
logger.debug("Setting store to False")
|
||||||
|
settings_service.settings.update_settings(STORE=False)
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def run(
|
def run(
|
||||||
host: str = typer.Option(
|
host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
|
||||||
"127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"
|
workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
|
||||||
),
|
|
||||||
workers: int = typer.Option(
|
|
||||||
1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"
|
|
||||||
),
|
|
||||||
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
|
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
|
||||||
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
|
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
|
||||||
components_path: Optional[Path] = typer.Option(
|
components_path: Optional[Path] = typer.Option(
|
||||||
|
|
@ -105,32 +107,17 @@ def run(
|
||||||
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: str = typer.Option(Path(__file__).parent / "config.yaml", help="Path to the configuration file."),
|
||||||
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(None, help="Path to the .env file containing environment variables."),
|
||||||
None, help="Path to the .env file containing environment variables."
|
log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
|
||||||
),
|
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"),
|
||||||
log_level: str = typer.Option(
|
|
||||||
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
|
||||||
),
|
|
||||||
log_file: Path = typer.Option(
|
|
||||||
"logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"
|
|
||||||
),
|
|
||||||
cache: Optional[str] = typer.Option(
|
cache: Optional[str] = typer.Option(
|
||||||
envvar="LANGFLOW_LANGCHAIN_CACHE",
|
envvar="LANGFLOW_LANGCHAIN_CACHE",
|
||||||
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
|
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
|
||||||
default=None,
|
default=None,
|
||||||
),
|
),
|
||||||
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)"),
|
||||||
# This variable does not work but is set by the .env file
|
|
||||||
# and works with Pydantic
|
|
||||||
# database_url: str = typer.Option(
|
|
||||||
# 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.",
|
||||||
|
|
@ -151,6 +138,11 @@ def run(
|
||||||
help="Run only the backend server without the frontend.",
|
help="Run only the backend server without the frontend.",
|
||||||
envvar="LANGFLOW_BACKEND_ONLY",
|
envvar="LANGFLOW_BACKEND_ONLY",
|
||||||
),
|
),
|
||||||
|
store: bool = typer.Option(
|
||||||
|
True,
|
||||||
|
help="Enables the store features.",
|
||||||
|
envvar="LANGFLOW_STORE",
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run the Langflow.
|
Run the Langflow.
|
||||||
|
|
@ -169,6 +161,7 @@ def run(
|
||||||
remove_api_keys=remove_api_keys,
|
remove_api_keys=remove_api_keys,
|
||||||
cache=cache,
|
cache=cache,
|
||||||
components_path=components_path,
|
components_path=components_path,
|
||||||
|
store=store,
|
||||||
)
|
)
|
||||||
# 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
|
||||||
|
|
@ -198,9 +191,7 @@ def run(
|
||||||
|
|
||||||
|
|
||||||
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
|
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
|
||||||
webapp_process = Process(
|
webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
|
||||||
target=run_langflow, args=(host, port, log_level, options, app)
|
|
||||||
)
|
|
||||||
webapp_process.start()
|
webapp_process.start()
|
||||||
status_code = 0
|
status_code = 0
|
||||||
while status_code != 200:
|
while status_code != 200:
|
||||||
|
|
@ -276,9 +267,7 @@ def print_banner(host, port):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a panel with the title and the info text, and a border around it
|
# Create a panel with the title and the info text, and a border around it
|
||||||
panel = Panel(
|
panel = Panel(f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False)
|
||||||
f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Print the banner with a separator line before and after
|
# Print the banner with a separator line before and after
|
||||||
rprint(panel)
|
rprint(panel)
|
||||||
|
|
@ -310,12 +299,8 @@ def run_langflow(host, port, log_level, options, app):
|
||||||
@app.command()
|
@app.command()
|
||||||
def superuser(
|
def superuser(
|
||||||
username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
|
username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
|
||||||
password: str = typer.Option(
|
password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
|
||||||
..., prompt=True, hide_input=True, help="Password for the superuser."
|
log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
|
||||||
),
|
|
||||||
log_level: str = typer.Option(
|
|
||||||
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
|
|
||||||
),
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a superuser.
|
Create a superuser.
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from sqlalchemy import pool
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
|
|
||||||
from langflow.services.database.manager import SQLModel
|
from langflow.services.database.service import SQLModel
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
"""Store updates
|
||||||
|
|
||||||
|
Revision ID: 7843803a87b5
|
||||||
|
Revises: eb5866d51fd2
|
||||||
|
Create Date: 2023-10-18 23:08:57.744906
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "7843803a87b5"
|
||||||
|
down_revision: Union[str, None] = "eb5866d51fd2"
|
||||||
|
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! ###
|
||||||
|
try:
|
||||||
|
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
|
with op.batch_alter_table("user", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(
|
||||||
|
sa.Column(
|
||||||
|
"store_api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table("user", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("store_api_key")
|
||||||
|
|
||||||
|
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("is_component")
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
"""Adds updated_at and folder cols
|
||||||
|
|
||||||
|
Revision ID: 7d2162acc8b2
|
||||||
|
Revises: f5ee9749d1a6
|
||||||
|
Create Date: 2023-11-21 20:56:53.998781
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '7d2162acc8b2'
|
||||||
|
down_revision: Union[str, None] = 'f5ee9749d1a6'
|
||||||
|
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! ###
|
||||||
|
with op.batch_alter_table('component', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index('ix_component_frontend_node_id')
|
||||||
|
batch_op.drop_index('ix_component_name')
|
||||||
|
|
||||||
|
op.drop_table('component')
|
||||||
|
op.drop_table('flowstyle')
|
||||||
|
with op.batch_alter_table('apikey', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
with op.batch_alter_table('flow', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column('folder', sqlmodel.sql.sqltypes.AutoString(), nullable=True))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
try:
|
||||||
|
with op.batch_alter_table('flow', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('folder')
|
||||||
|
batch_op.drop_column('updated_at')
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
with op.batch_alter_table('apikey', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column('name',
|
||||||
|
existing_type=sa.VARCHAR(),
|
||||||
|
nullable=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
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')
|
||||||
|
)
|
||||||
|
op.create_table('component',
|
||||||
|
sa.Column('id', sa.CHAR(length=32), nullable=False),
|
||||||
|
sa.Column('frontend_node_id', sa.CHAR(length=32), nullable=False),
|
||||||
|
sa.Column('name', sa.VARCHAR(), nullable=False),
|
||||||
|
sa.Column('description', sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column('python_code', sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column('return_type', sa.VARCHAR(), nullable=True),
|
||||||
|
sa.Column('is_disabled', sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column('is_read_only', sa.BOOLEAN(), nullable=False),
|
||||||
|
sa.Column('create_at', sa.DATETIME(), nullable=False),
|
||||||
|
sa.Column('update_at', sa.DATETIME(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
with op.batch_alter_table('component', schema=None) as batch_op:
|
||||||
|
batch_op.create_index('ix_component_name', ['name'], unique=False)
|
||||||
|
batch_op.create_index('ix_component_frontend_node_id', ['frontend_node_id'], unique=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
pass
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -9,6 +9,7 @@ from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import exc
|
||||||
import sqlmodel # noqa: F401
|
import sqlmodel # noqa: F401
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
|
|
@ -20,16 +21,21 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
connection = op.get_bind()
|
||||||
try:
|
try:
|
||||||
op.drop_table("flowstyle")
|
op.drop_table("flowstyle")
|
||||||
with op.batch_alter_table("component", schema=None) as batch_op:
|
with op.batch_alter_table("component", schema=None) as batch_op:
|
||||||
batch_op.drop_index("ix_component_frontend_node_id")
|
batch_op.drop_index("ix_component_frontend_node_id")
|
||||||
batch_op.drop_index("ix_component_name")
|
batch_op.drop_index("ix_component_name")
|
||||||
|
except exc.SQLAlchemyError:
|
||||||
|
connection.execute("ROLLBACK")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
op.drop_table("component")
|
op.drop_table("component")
|
||||||
|
except exc.SQLAlchemyError:
|
||||||
|
connection.execute("ROLLBACK")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""User id can be null in Flow
|
||||||
|
|
||||||
|
Revision ID: f5ee9749d1a6
|
||||||
|
Revises: 7843803a87b5
|
||||||
|
Create Date: 2023-10-18 23:12:27.297016
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "f5ee9749d1a6"
|
||||||
|
down_revision: Union[str, None] = "7843803a87b5"
|
||||||
|
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! ###
|
||||||
|
try:
|
||||||
|
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||||
|
batch_op.alter_column(
|
||||||
|
"user_id", existing_type=sa.CHAR(length=32), nullable=True
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
try:
|
||||||
|
with op.batch_alter_table("flow", schema=None) as batch_op:
|
||||||
|
batch_op.alter_column(
|
||||||
|
"user_id", existing_type=sa.CHAR(length=32), nullable=False
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
@ -5,7 +5,7 @@ from langflow.api.v1 import (
|
||||||
endpoints_router,
|
endpoints_router,
|
||||||
validate_router,
|
validate_router,
|
||||||
flows_router,
|
flows_router,
|
||||||
component_router,
|
store_router,
|
||||||
users_router,
|
users_router,
|
||||||
api_key_router,
|
api_key_router,
|
||||||
login_router,
|
login_router,
|
||||||
|
|
@ -17,7 +17,7 @@ router = APIRouter(
|
||||||
router.include_router(chat_router)
|
router.include_router(chat_router)
|
||||||
router.include_router(endpoints_router)
|
router.include_router(endpoints_router)
|
||||||
router.include_router(validate_router)
|
router.include_router(validate_router)
|
||||||
router.include_router(component_router)
|
router.include_router(store_router)
|
||||||
router.include_router(flows_router)
|
router.include_router(flows_router)
|
||||||
router.include_router(users_router)
|
router.include_router(users_router)
|
||||||
router.include_router(api_key_router)
|
router.include_router(api_key_router)
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ API_WORDS = ["api", "key", "token"]
|
||||||
|
|
||||||
|
|
||||||
def has_api_terms(word: str):
|
def has_api_terms(word: str):
|
||||||
return "api" in word and (
|
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
|
||||||
"key" in word or ("token" in word and "tokens" not in word)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_api_keys(flow: dict):
|
def remove_api_keys(flow: dict):
|
||||||
|
|
@ -14,11 +12,7 @@ def remove_api_keys(flow: dict):
|
||||||
node_data = node.get("data").get("node")
|
node_data = node.get("data").get("node")
|
||||||
template = node_data.get("template")
|
template = node_data.get("template")
|
||||||
for value in template.values():
|
for value in template.values():
|
||||||
if (
|
if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
|
||||||
isinstance(value, dict)
|
|
||||||
and has_api_terms(value["name"])
|
|
||||||
and value.get("password")
|
|
||||||
):
|
|
||||||
value["value"] = None
|
value["value"] = None
|
||||||
|
|
||||||
return flow
|
return flow
|
||||||
|
|
@ -39,9 +33,7 @@ def build_input_keys_response(langchain_object, artifacts):
|
||||||
input_keys_response["input_keys"][key] = value
|
input_keys_response["input_keys"][key] = value
|
||||||
# If the object has memory, that memory will have a memory_variables attribute
|
# If the object has memory, that memory will have a memory_variables attribute
|
||||||
# memory variables should be removed from the input keys
|
# memory variables should be removed from the input keys
|
||||||
if hasattr(langchain_object, "memory") and hasattr(
|
if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
|
||||||
langchain_object.memory, "memory_variables"
|
|
||||||
):
|
|
||||||
# Remove memory variables from input keys
|
# Remove memory variables from input keys
|
||||||
input_keys_response["input_keys"] = {
|
input_keys_response["input_keys"] = {
|
||||||
key: value
|
key: value
|
||||||
|
|
@ -51,9 +43,7 @@ def build_input_keys_response(langchain_object, artifacts):
|
||||||
# Add memory variables to memory_keys
|
# Add memory variables to memory_keys
|
||||||
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
||||||
|
|
||||||
if hasattr(langchain_object, "prompt") and hasattr(
|
if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
|
||||||
langchain_object.prompt, "template"
|
|
||||||
):
|
|
||||||
input_keys_response["template"] = langchain_object.prompt.template
|
input_keys_response["template"] = langchain_object.prompt.template
|
||||||
|
|
||||||
return input_keys_response
|
return input_keys_response
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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.components import router as component_router
|
from langflow.api.v1.store import router as store_router
|
||||||
from langflow.api.v1.users import router as users_router
|
from langflow.api.v1.users import router as users_router
|
||||||
from langflow.api.v1.api_key import router as api_key_router
|
from langflow.api.v1.api_key import router as api_key_router
|
||||||
from langflow.api.v1.login import router as login_router
|
from langflow.api.v1.login import router as login_router
|
||||||
|
|
@ -10,7 +10,7 @@ from langflow.api.v1.login import router as login_router
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"chat_router",
|
"chat_router",
|
||||||
"endpoints_router",
|
"endpoints_router",
|
||||||
"component_router",
|
"store_router",
|
||||||
"validate_router",
|
"validate_router",
|
||||||
"flows_router",
|
"flows_router",
|
||||||
"users_router",
|
"users_router",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from fastapi import APIRouter, HTTPException, Depends
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
from langflow.api.v1.schemas import ApiKeysResponse
|
from langflow.api.v1.schemas import ApiKeysResponse, ApiKeyCreateRequest
|
||||||
from langflow.services.auth.utils import get_current_active_user
|
from langflow.services.auth import utils as auth_utils
|
||||||
from langflow.services.database.models.api_key.api_key import (
|
from langflow.services.database.models.api_key.api_key import (
|
||||||
ApiKeyCreate,
|
ApiKeyCreate,
|
||||||
UnmaskedApiKeyRead,
|
UnmaskedApiKeyRead,
|
||||||
|
|
@ -14,9 +14,17 @@ from langflow.services.database.models.api_key.crud import (
|
||||||
delete_api_key,
|
delete_api_key,
|
||||||
)
|
)
|
||||||
from langflow.services.database.models.user.user import User
|
from langflow.services.database.models.user.user import User
|
||||||
from langflow.services.getters import get_session
|
from langflow.services.deps import (
|
||||||
|
get_session,
|
||||||
|
get_settings_service,
|
||||||
|
)
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
router = APIRouter(tags=["APIKey"], prefix="/api_key")
|
router = APIRouter(tags=["APIKey"], prefix="/api_key")
|
||||||
|
|
||||||
|
|
@ -24,7 +32,7 @@ router = APIRouter(tags=["APIKey"], prefix="/api_key")
|
||||||
@router.get("/", response_model=ApiKeysResponse)
|
@router.get("/", response_model=ApiKeysResponse)
|
||||||
def get_api_keys_route(
|
def get_api_keys_route(
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(auth_utils.get_current_active_user),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
|
|
@ -38,7 +46,7 @@ def get_api_keys_route(
|
||||||
@router.post("/", response_model=UnmaskedApiKeyRead)
|
@router.post("/", response_model=UnmaskedApiKeyRead)
|
||||||
def create_api_key_route(
|
def create_api_key_route(
|
||||||
req: ApiKeyCreate,
|
req: ApiKeyCreate,
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(auth_utils.get_current_active_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
|
@ -51,7 +59,7 @@ def create_api_key_route(
|
||||||
@router.delete("/{api_key_id}")
|
@router.delete("/{api_key_id}")
|
||||||
def delete_api_key_route(
|
def delete_api_key_route(
|
||||||
api_key_id: UUID,
|
api_key_id: UUID,
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(auth_utils.get_current_active_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
|
@ -59,3 +67,21 @@ def delete_api_key_route(
|
||||||
return {"detail": "API Key deleted"}
|
return {"detail": "API Key deleted"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/store")
|
||||||
|
def save_store_api_key(
|
||||||
|
api_key_request: ApiKeyCreateRequest,
|
||||||
|
current_user: User = Depends(auth_utils.get_current_active_user),
|
||||||
|
db: Session = Depends(get_session),
|
||||||
|
settings_service=Depends(get_settings_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
api_key = api_key_request.api_key
|
||||||
|
# Encrypt the API key
|
||||||
|
encrypted = auth_utils.encrypt_api_key(api_key, settings_service=settings_service)
|
||||||
|
current_user.store_api_key = encrypted
|
||||||
|
db.commit()
|
||||||
|
return {"detail": "API Key saved"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import field_validator, BaseModel
|
||||||
|
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
from langchain.prompts import PromptTemplate
|
from langchain.prompts import PromptTemplate
|
||||||
|
|
@ -30,11 +30,13 @@ class CodeValidationResponse(BaseModel):
|
||||||
imports: dict
|
imports: dict
|
||||||
function: dict
|
function: dict
|
||||||
|
|
||||||
@validator("imports")
|
@field_validator("imports")
|
||||||
|
@classmethod
|
||||||
def validate_imports(cls, v):
|
def validate_imports(cls, v):
|
||||||
return v or {"errors": []}
|
return v or {"errors": []}
|
||||||
|
|
||||||
@validator("function")
|
@field_validator("function")
|
||||||
|
@classmethod
|
||||||
def validate_function(cls, v):
|
def validate_function(cls, v):
|
||||||
return v or {"errors": []}
|
return v or {"errors": []}
|
||||||
|
|
||||||
|
|
@ -79,9 +81,7 @@ def validate_prompt(template: str):
|
||||||
# Check if there are invalid characters in the input_variables
|
# Check if there are invalid characters in the input_variables
|
||||||
input_variables = check_input_variables(input_variables)
|
input_variables = check_input_variables(input_variables)
|
||||||
if any(var in INVALID_NAMES for var in input_variables):
|
if any(var in INVALID_NAMES for var in input_variables):
|
||||||
raise ValueError(
|
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
|
||||||
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
PromptTemplate(template=template, input_variables=input_variables)
|
PromptTemplate(template=template, input_variables=input_variables)
|
||||||
|
|
@ -132,9 +132,7 @@ def check_input_variables(input_variables: list):
|
||||||
return input_variables
|
return input_variables
|
||||||
|
|
||||||
|
|
||||||
def build_error_message(
|
def build_error_message(input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables):
|
||||||
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
|
|
||||||
):
|
|
||||||
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
|
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
|
||||||
error_string = f"Invalid input variables: {input_variables_str}. "
|
error_string = f"Invalid input variables: {input_variables_str}. "
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
||||||
|
|
||||||
from langflow.api.v1.schemas import ChatResponse, PromptResponse
|
|
||||||
|
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
from langflow.services.getters import get_chat_service
|
|
||||||
|
|
||||||
|
|
||||||
from langflow.utils.util import remove_ansi_escape_codes
|
|
||||||
from langchain.schema import AgentAction, AgentFinish
|
from langchain.schema import AgentAction, AgentFinish
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from langflow.api.v1.schemas import ChatResponse, PromptResponse
|
||||||
|
from langflow.services.deps import get_chat_service
|
||||||
|
from langflow.utils.util import remove_ansi_escape_codes
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
||||||
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
|
|
@ -26,18 +22,16 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
|
|
||||||
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
|
|
||||||
async def on_tool_start(
|
async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> Any:
|
||||||
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
|
|
||||||
) -> Any:
|
|
||||||
"""Run when tool starts running."""
|
"""Run when tool starts running."""
|
||||||
resp = ChatResponse(
|
resp = ChatResponse(
|
||||||
message="",
|
message="",
|
||||||
type="stream",
|
type="stream",
|
||||||
intermediate_steps=f"Tool input: {input_str}",
|
intermediate_steps=f"Tool input: {input_str}",
|
||||||
)
|
)
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
|
|
||||||
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
|
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
|
||||||
"""Run when tool ends running."""
|
"""Run when tool ends running."""
|
||||||
|
|
@ -68,7 +62,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
try:
|
try:
|
||||||
# This is to emulate the stream of tokens
|
# This is to emulate the stream of tokens
|
||||||
for resp in resps:
|
for resp in resps:
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error sending response: {exc}")
|
logger.error(f"Error sending response: {exc}")
|
||||||
|
|
||||||
|
|
@ -94,7 +88,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
resp = PromptResponse(
|
resp = PromptResponse(
|
||||||
prompt=text,
|
prompt=text,
|
||||||
)
|
)
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
self.chat_service.chat_history.add_message(self.client_id, resp)
|
self.chat_service.chat_history.add_message(self.client_id, resp)
|
||||||
|
|
||||||
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
||||||
|
|
@ -105,10 +99,10 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
logs = log.split("\n")
|
logs = log.split("\n")
|
||||||
for log in logs:
|
for log in logs:
|
||||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
else:
|
else:
|
||||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
|
|
||||||
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
|
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
|
||||||
"""Run on agent end."""
|
"""Run on agent end."""
|
||||||
|
|
@ -117,7 +111,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
type="stream",
|
type="stream",
|
||||||
intermediate_steps=finish.log,
|
intermediate_steps=finish.log,
|
||||||
)
|
)
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.model_dump())
|
||||||
|
|
||||||
|
|
||||||
class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
||||||
|
|
@ -132,5 +126,5 @@ class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
coroutine = self.websocket.send_json(resp.dict())
|
coroutine = self.websocket.send_json(resp.model_dump())
|
||||||
asyncio.run_coroutine_threadsafe(coroutine, loop)
|
asyncio.run_coroutine_threadsafe(coroutine, loop)
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,20 @@ from fastapi import (
|
||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
from loguru import logger
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
from langflow.api.utils import build_input_keys_response
|
from langflow.api.utils import build_input_keys_response
|
||||||
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData
|
||||||
|
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
from langflow.services.auth.utils import get_current_active_user, get_current_user
|
from langflow.services.auth.utils import (
|
||||||
|
get_current_active_user,
|
||||||
|
get_current_user_by_jwt,
|
||||||
|
)
|
||||||
|
from langflow.services.cache.service import BaseCacheService
|
||||||
from langflow.services.cache.utils import update_build_status
|
from langflow.services.cache.utils import update_build_status
|
||||||
from loguru import logger
|
from langflow.services.chat.service import ChatService
|
||||||
from langflow.services.getters import get_chat_service, get_session, get_cache_service
|
from langflow.services.deps import get_cache_service, get_chat_service, get_session
|
||||||
from sqlmodel import Session
|
|
||||||
from langflow.services.chat.manager import ChatService
|
|
||||||
from langflow.services.cache.manager import BaseCacheService
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(tags=["Chat"])
|
router = APIRouter(tags=["Chat"])
|
||||||
|
|
||||||
|
|
@ -34,16 +36,12 @@ async def chat(
|
||||||
):
|
):
|
||||||
"""Websocket endpoint for chat."""
|
"""Websocket endpoint for chat."""
|
||||||
try:
|
try:
|
||||||
|
user = await get_current_user_by_jwt(token, db)
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
user = await get_current_user(token, db)
|
|
||||||
if not user:
|
if not user:
|
||||||
await websocket.close(
|
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
||||||
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
|
||||||
)
|
|
||||||
if not user.is_active:
|
if not user.is_active:
|
||||||
await websocket.close(
|
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
||||||
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
|
||||||
)
|
|
||||||
|
|
||||||
if client_id in chat_service.cache_service:
|
if client_id in chat_service.cache_service:
|
||||||
await chat_service.handle_websocket(client_id, websocket)
|
await chat_service.handle_websocket(client_id, websocket)
|
||||||
|
|
@ -59,9 +57,7 @@ async def chat(
|
||||||
logger.error(f"Error in chat websocket: {exc}")
|
logger.error(f"Error in chat websocket: {exc}")
|
||||||
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
|
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
|
||||||
if "Could not validate credentials" in str(exc):
|
if "Could not validate credentials" in str(exc):
|
||||||
await websocket.close(
|
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
||||||
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
|
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
|
||||||
|
|
||||||
|
|
@ -103,15 +99,10 @@ async def init_build(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/build/{flow_id}/status", response_model=BuiltResponse)
|
@router.get("/build/{flow_id}/status", response_model=BuiltResponse)
|
||||||
async def build_status(
|
async def build_status(flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)):
|
||||||
flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)
|
|
||||||
):
|
|
||||||
"""Check the flow_id is in the cache_service."""
|
"""Check the flow_id is in the cache_service."""
|
||||||
try:
|
try:
|
||||||
built = (
|
built = flow_id in cache_service and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
|
||||||
flow_id in cache_service
|
|
||||||
and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
|
|
||||||
)
|
|
||||||
|
|
||||||
return BuiltResponse(
|
return BuiltResponse(
|
||||||
built=built,
|
built=built,
|
||||||
|
|
@ -160,6 +151,11 @@ async def stream_build(
|
||||||
number_of_nodes = len(graph.nodes)
|
number_of_nodes = len(graph.nodes)
|
||||||
update_build_status(cache_service, flow_id, BuildStatus.IN_PROGRESS)
|
update_build_status(cache_service, flow_id, BuildStatus.IN_PROGRESS)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_id = cache_service[flow_id]["user_id"]
|
||||||
|
except KeyError:
|
||||||
|
logger.debug("No user_id found in cache_service")
|
||||||
|
user_id = None
|
||||||
for i, vertex in enumerate(graph.generator_build(), 1):
|
for i, vertex in enumerate(graph.generator_build(), 1):
|
||||||
try:
|
try:
|
||||||
log_dict = {
|
log_dict = {
|
||||||
|
|
@ -167,15 +163,13 @@ async def stream_build(
|
||||||
}
|
}
|
||||||
yield str(StreamData(event="log", data=log_dict))
|
yield str(StreamData(event="log", data=log_dict))
|
||||||
if vertex.is_task:
|
if vertex.is_task:
|
||||||
vertex = try_running_celery_task(vertex)
|
vertex = await try_running_celery_task(vertex, user_id)
|
||||||
else:
|
else:
|
||||||
vertex.build()
|
await vertex.build(user_id=user_id)
|
||||||
params = vertex._built_object_repr()
|
params = vertex._built_object_repr()
|
||||||
valid = True
|
valid = True
|
||||||
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
||||||
logger.debug(
|
logger.debug(f"Output: {params[:100]}{'...' if len(params) > 100 else ''}")
|
||||||
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
|
|
||||||
)
|
|
||||||
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
|
||||||
|
|
@ -187,9 +181,7 @@ async def stream_build(
|
||||||
valid = False
|
valid = False
|
||||||
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
||||||
|
|
||||||
vertex_id = (
|
vertex_id = vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
||||||
vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
|
||||||
)
|
|
||||||
if vertex_id in graph.top_level_nodes:
|
if vertex_id in graph.top_level_nodes:
|
||||||
response = {
|
response = {
|
||||||
"valid": valid,
|
"valid": valid,
|
||||||
|
|
@ -200,12 +192,10 @@ async def stream_build(
|
||||||
|
|
||||||
yield str(StreamData(event="message", data=response))
|
yield str(StreamData(event="message", data=response))
|
||||||
|
|
||||||
langchain_object = graph.build()
|
langchain_object = await graph.build()
|
||||||
# Now we need to check the input_keys to send them to the client
|
# Now we need to check the input_keys to send them to the client
|
||||||
if hasattr(langchain_object, "input_keys"):
|
if hasattr(langchain_object, "input_keys"):
|
||||||
input_keys_response = build_input_keys_response(
|
input_keys_response = build_input_keys_response(langchain_object, artifacts)
|
||||||
langchain_object, artifacts
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
input_keys_response = {
|
input_keys_response = {
|
||||||
"input_keys": None,
|
"input_keys": None,
|
||||||
|
|
@ -233,7 +223,7 @@ async def stream_build(
|
||||||
raise HTTPException(status_code=500, detail=str(exc))
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
def try_running_celery_task(vertex):
|
async def try_running_celery_task(vertex, user_id):
|
||||||
# Try running the task in celery
|
# Try running the task in celery
|
||||||
# and set the task_id to the local vertex
|
# and set the task_id to the local vertex
|
||||||
# if it fails, run the task locally
|
# if it fails, run the task locally
|
||||||
|
|
@ -245,5 +235,5 @@ def try_running_celery_task(vertex):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug(f"Error running task in celery: {exc}")
|
logger.debug(f"Error running task in celery: {exc}")
|
||||||
vertex.task_id = None
|
vertex.task_id = None
|
||||||
vertex.build()
|
await vertex.build(user_id=user_id)
|
||||||
return vertex
|
return vertex
|
||||||
|
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
from datetime import timezone
|
|
||||||
from typing import List
|
|
||||||
from uuid import UUID
|
|
||||||
from langflow.services.database.models.component import Component, ComponentModel
|
|
||||||
from langflow.services.getters import get_session
|
|
||||||
from sqlmodel import Session, select
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
|
||||||
from sqlalchemy.exc import IntegrityError
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
COMPONENT_NOT_FOUND = "Component not found"
|
|
||||||
COMPONENT_ALREADY_EXISTS = "A component with the same id already exists."
|
|
||||||
COMPONENT_DELETED = "Component deleted"
|
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/components", tags=["Components"])
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", response_model=Component)
|
|
||||||
def create_component(component: ComponentModel, db: Session = Depends(get_session)):
|
|
||||||
db_component = Component(**component.dict())
|
|
||||||
try:
|
|
||||||
db.add(db_component)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_component)
|
|
||||||
except IntegrityError as e:
|
|
||||||
db.rollback()
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400,
|
|
||||||
detail=COMPONENT_ALREADY_EXISTS,
|
|
||||||
) from e
|
|
||||||
return db_component
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{component_id}", response_model=Component)
|
|
||||||
def read_component(component_id: UUID, db: Session = Depends(get_session)):
|
|
||||||
if component := db.get(Component, component_id):
|
|
||||||
return component
|
|
||||||
else:
|
|
||||||
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=List[Component])
|
|
||||||
def read_components(skip: int = 0, limit: int = 50, db: Session = Depends(get_session)):
|
|
||||||
query = select(Component)
|
|
||||||
query = query.offset(skip).limit(limit)
|
|
||||||
|
|
||||||
return db.execute(query).fetchall()
|
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{component_id}", response_model=Component)
|
|
||||||
def update_component(
|
|
||||||
component_id: UUID, component: ComponentModel, db: Session = Depends(get_session)
|
|
||||||
):
|
|
||||||
db_component = db.get(Component, component_id)
|
|
||||||
if not db_component:
|
|
||||||
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
|
|
||||||
component_data = component.dict(exclude_unset=True)
|
|
||||||
|
|
||||||
for key, value in component_data.items():
|
|
||||||
setattr(db_component, key, value)
|
|
||||||
|
|
||||||
db_component.update_at = datetime.now(timezone.utc)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_component)
|
|
||||||
return db_component
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{component_id}")
|
|
||||||
def delete_component(component_id: UUID, db: Session = Depends(get_session)):
|
|
||||||
component = db.get(Component, component_id)
|
|
||||||
if not component:
|
|
||||||
raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND)
|
|
||||||
db.delete(component)
|
|
||||||
db.commit()
|
|
||||||
return {"detail": COMPONENT_DELETED}
|
|
||||||
|
|
@ -1,33 +1,30 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, Optional, Union
|
from typing import Annotated, Optional, Union
|
||||||
from langflow.services.auth.utils import api_key_security, get_current_active_user
|
|
||||||
|
|
||||||
|
|
||||||
from langflow.services.cache.utils import save_uploaded_file
|
|
||||||
from langflow.services.database.models.flow import Flow
|
|
||||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
|
||||||
from langflow.services.database.models.user.user import User
|
|
||||||
from langflow.services.getters import (
|
|
||||||
get_session_service,
|
|
||||||
get_settings_service,
|
|
||||||
get_task_service,
|
|
||||||
)
|
|
||||||
from loguru import logger
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body, status
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from langflow.interface.custom.custom_component import CustomComponent
|
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
|
CustomComponentCode,
|
||||||
ProcessResponse,
|
ProcessResponse,
|
||||||
TaskResponse,
|
TaskResponse,
|
||||||
TaskStatusResponse,
|
TaskStatusResponse,
|
||||||
UploadFileResponse,
|
UploadFileResponse,
|
||||||
CustomComponentCode,
|
|
||||||
)
|
)
|
||||||
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
|
from langflow.interface.custom.directory_reader import DirectoryReader
|
||||||
from langflow.services.getters import get_session
|
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||||
|
from langflow.services.auth.utils import api_key_security, get_current_active_user
|
||||||
|
from langflow.services.cache.utils import save_uploaded_file
|
||||||
|
from langflow.services.database.models.flow import Flow
|
||||||
|
from langflow.services.database.models.user.user import User
|
||||||
|
from langflow.services.deps import (
|
||||||
|
get_session,
|
||||||
|
get_session_service,
|
||||||
|
get_settings_service,
|
||||||
|
get_task_service,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from langflow.worker import process_graph_cached_task
|
from langflow.worker import process_graph_cached_task
|
||||||
|
|
@ -39,8 +36,7 @@ except ImportError:
|
||||||
|
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
from langflow.services.task.service import TaskService
|
||||||
from langflow.services.task.manager import TaskService
|
|
||||||
|
|
||||||
# build router
|
# build router
|
||||||
router = APIRouter(tags=["Base"])
|
router = APIRouter(tags=["Base"])
|
||||||
|
|
@ -92,12 +88,7 @@ async def process(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the flow that matches the flow_id and belongs to the user
|
# Get the flow that matches the flow_id and belongs to the user
|
||||||
flow = (
|
flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
||||||
session.query(Flow)
|
|
||||||
.filter(Flow.id == flow_id)
|
|
||||||
.filter(Flow.user_id == api_key_user.id)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
if flow is None:
|
if flow is None:
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
|
|
||||||
|
|
@ -111,9 +102,7 @@ async def process(
|
||||||
logger.error(f"Error processing tweaks: {exc}")
|
logger.error(f"Error processing tweaks: {exc}")
|
||||||
if sync:
|
if sync:
|
||||||
task_id, result = await task_service.launch_and_await_task(
|
task_id, result = await task_service.launch_and_await_task(
|
||||||
process_graph_cached_task
|
process_graph_cached_task if task_service.use_celery else process_graph_cached,
|
||||||
if task_service.use_celery
|
|
||||||
else process_graph_cached,
|
|
||||||
graph_data,
|
graph_data,
|
||||||
inputs,
|
inputs,
|
||||||
clear_cache,
|
clear_cache,
|
||||||
|
|
@ -133,13 +122,9 @@ async def process(
|
||||||
)
|
)
|
||||||
if session_id is None:
|
if session_id is None:
|
||||||
# Generate a session ID
|
# Generate a session ID
|
||||||
session_id = get_session_service().generate_key(
|
session_id = get_session_service().generate_key(session_id=session_id, data_graph=graph_data)
|
||||||
session_id=session_id, data_graph=graph_data
|
|
||||||
)
|
|
||||||
task_id, task = await task_service.launch_task(
|
task_id, task = await task_service.launch_task(
|
||||||
process_graph_cached_task
|
process_graph_cached_task if task_service.use_celery else process_graph_cached,
|
||||||
if task_service.use_celery
|
|
||||||
else process_graph_cached,
|
|
||||||
graph_data,
|
graph_data,
|
||||||
inputs,
|
inputs,
|
||||||
clear_cache,
|
clear_cache,
|
||||||
|
|
@ -162,18 +147,12 @@ async def process(
|
||||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||||
if "badly formed hexadecimal UUID string" in str(exc):
|
if "badly formed hexadecimal UUID string" in str(exc):
|
||||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
if f"Flow {flow_id} not found" in str(exc):
|
if f"Flow {flow_id} not found" in str(exc):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
@ -227,12 +206,32 @@ def get_version():
|
||||||
@router.post("/custom_component", status_code=HTTPStatus.OK)
|
@router.post("/custom_component", status_code=HTTPStatus.OK)
|
||||||
async def custom_component(
|
async def custom_component(
|
||||||
raw_code: CustomComponentCode,
|
raw_code: CustomComponentCode,
|
||||||
|
user: User = Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
from langflow.interface.types import (
|
from langflow.interface.types import (
|
||||||
build_langchain_template_custom_component,
|
build_langchain_template_custom_component,
|
||||||
)
|
)
|
||||||
|
|
||||||
extractor = CustomComponent(code=raw_code.code)
|
extractor = CustomComponent(code=raw_code.code)
|
||||||
extractor.is_check_valid()
|
extractor.validate()
|
||||||
|
|
||||||
return build_langchain_template_custom_component(extractor)
|
return build_langchain_template_custom_component(extractor, user_id=user.id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
||||||
|
async def reload_custom_component(path: str):
|
||||||
|
from langflow.interface.types import (
|
||||||
|
build_langchain_template_custom_component,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
reader = DirectoryReader("")
|
||||||
|
valid, content = reader.process_file(path)
|
||||||
|
if not valid:
|
||||||
|
raise ValueError(content)
|
||||||
|
|
||||||
|
extractor = CustomComponent(code=content)
|
||||||
|
extractor.validate()
|
||||||
|
return build_langchain_template_custom_component(extractor, user_id=user.id)
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
|
from datetime import datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import orjson
|
||||||
|
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
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.services.auth.utils import get_current_active_user
|
from langflow.services.auth.utils import get_current_active_user
|
||||||
from langflow.services.database.models.flow import (
|
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
|
||||||
Flow,
|
|
||||||
FlowCreate,
|
|
||||||
FlowRead,
|
|
||||||
FlowUpdate,
|
|
||||||
)
|
|
||||||
from langflow.services.database.models.user.user import User
|
from langflow.services.database.models.user.user import User
|
||||||
from langflow.services.getters import get_session
|
from langflow.services.deps import get_session, get_settings_service
|
||||||
from langflow.services.getters import get_settings_service
|
|
||||||
import orjson
|
|
||||||
from sqlmodel import Session
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
|
||||||
|
|
||||||
from fastapi import File, UploadFile
|
|
||||||
|
|
||||||
# build router
|
# build router
|
||||||
router = APIRouter(prefix="/flows", tags=["Flows"])
|
router = APIRouter(prefix="/flows", tags=["Flows"])
|
||||||
|
|
@ -35,7 +29,8 @@ def create_flow(
|
||||||
if flow.user_id is None:
|
if flow.user_id is None:
|
||||||
flow.user_id = current_user.id
|
flow.user_id = current_user.id
|
||||||
|
|
||||||
db_flow = Flow.from_orm(flow)
|
db_flow = Flow.model_validate(flow, from_attributes=True)
|
||||||
|
db_flow.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
session.add(db_flow)
|
session.add(db_flow)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
@ -46,7 +41,6 @@ def create_flow(
|
||||||
@router.get("/", response_model=list[FlowRead], status_code=200)
|
@router.get("/", response_model=list[FlowRead], status_code=200)
|
||||||
def read_flows(
|
def read_flows(
|
||||||
*,
|
*,
|
||||||
session: Session = Depends(get_session),
|
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""Read all flows."""
|
"""Read all flows."""
|
||||||
|
|
@ -65,12 +59,7 @@ def read_flow(
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""Read a flow."""
|
"""Read a flow."""
|
||||||
if user_flow := (
|
if user_flow := (session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == current_user.id).first()):
|
||||||
session.query(Flow)
|
|
||||||
.filter(Flow.id == flow_id)
|
|
||||||
.filter(Flow.user_id == current_user.id)
|
|
||||||
.first()
|
|
||||||
):
|
|
||||||
return user_flow
|
return user_flow
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=404, detail="Flow not found")
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||||||
|
|
@ -90,12 +79,13 @@ def update_flow(
|
||||||
db_flow = read_flow(session=session, flow_id=flow_id, current_user=current_user)
|
db_flow = read_flow(session=session, flow_id=flow_id, current_user=current_user)
|
||||||
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.model_dump(exclude_unset=True)
|
||||||
if settings_service.settings.REMOVE_API_KEYS:
|
if settings_service.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():
|
||||||
if value is not None:
|
if value is not None:
|
||||||
setattr(db_flow, key, value)
|
setattr(db_flow, key, value)
|
||||||
|
db_flow.updated_at = datetime.utcnow()
|
||||||
session.add(db_flow)
|
session.add(db_flow)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(db_flow)
|
session.refresh(db_flow)
|
||||||
|
|
@ -169,5 +159,5 @@ async def download_file(
|
||||||
current_user: User = Depends(get_current_active_user),
|
current_user: User = Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""Download all flows as a file."""
|
"""Download all flows as a file."""
|
||||||
flows = read_flows(session=session, current_user=current_user)
|
flows = read_flows(current_user=current_user)
|
||||||
return FlowListRead(flows=flows)
|
return FlowListRead(flows=flows)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
from sqlmodel import Session
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
from langflow.services.getters import get_session
|
|
||||||
from langflow.api.v1.schemas import Token
|
from langflow.api.v1.schemas import Token
|
||||||
from langflow.services.auth.utils import (
|
from langflow.services.auth.utils import (
|
||||||
authenticate_user,
|
authenticate_user,
|
||||||
create_user_tokens,
|
|
||||||
create_refresh_token,
|
create_refresh_token,
|
||||||
create_user_longterm_token,
|
create_user_longterm_token,
|
||||||
get_current_active_user,
|
create_user_tokens,
|
||||||
)
|
)
|
||||||
|
from langflow.services.deps import get_session, get_settings_service
|
||||||
from langflow.services.getters import get_settings_service
|
|
||||||
|
|
||||||
router = APIRouter(tags=["Login"])
|
router = APIRouter(tags=["Login"])
|
||||||
|
|
||||||
|
|
@ -44,9 +41,7 @@ async def login_to_get_access_token(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/auto_login")
|
@router.get("/auto_login")
|
||||||
async def auto_login(
|
async def auto_login(db: Session = Depends(get_session), settings_service=Depends(get_settings_service)):
|
||||||
db: Session = Depends(get_session), settings_service=Depends(get_settings_service)
|
|
||||||
):
|
|
||||||
if settings_service.auth_settings.AUTO_LOGIN:
|
if settings_service.auth_settings.AUTO_LOGIN:
|
||||||
return create_user_longterm_token(db)
|
return create_user_longterm_token(db)
|
||||||
|
|
||||||
|
|
@ -60,9 +55,7 @@ async def auto_login(
|
||||||
|
|
||||||
|
|
||||||
@router.post("/refresh")
|
@router.post("/refresh")
|
||||||
async def refresh_token(
|
async def refresh_token(token: str):
|
||||||
token: str, current_user: Session = Depends(get_current_active_user)
|
|
||||||
):
|
|
||||||
if token:
|
if token:
|
||||||
return create_refresh_token(token)
|
return create_refresh_token(token)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from langflow.services.database.models.flow import FlowCreate, FlowRead
|
||||||
from langflow.services.database.models.user import UserRead
|
from langflow.services.database.models.user import UserRead
|
||||||
from langflow.services.database.models.base import orjson_dumps
|
from langflow.services.database.models.base import orjson_dumps
|
||||||
|
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
|
||||||
class BuildStatus(Enum):
|
class BuildStatus(Enum):
|
||||||
|
|
@ -91,7 +91,8 @@ class ChatResponse(ChatMessage):
|
||||||
is_bot: bool = True
|
is_bot: bool = True
|
||||||
files: list = []
|
files: list = []
|
||||||
|
|
||||||
@validator("type")
|
@field_validator("type")
|
||||||
|
@classmethod
|
||||||
def validate_message_type(cls, v):
|
def validate_message_type(cls, v):
|
||||||
if v not in ["start", "stream", "end", "error", "info", "file"]:
|
if v not in ["start", "stream", "end", "error", "info", "file"]:
|
||||||
raise ValueError("type must be start, stream, end, error, info, or file")
|
raise ValueError("type must be start, stream, end, error, info, or file")
|
||||||
|
|
@ -109,12 +110,13 @@ class PromptResponse(ChatMessage):
|
||||||
class FileResponse(ChatMessage):
|
class FileResponse(ChatMessage):
|
||||||
"""File response schema."""
|
"""File response schema."""
|
||||||
|
|
||||||
data: Any
|
data: Any = None
|
||||||
data_type: str
|
data_type: str
|
||||||
type: str = "file"
|
type: str = "file"
|
||||||
is_bot: bool = True
|
is_bot: bool = True
|
||||||
|
|
||||||
@validator("data_type")
|
@field_validator("data_type")
|
||||||
|
@classmethod
|
||||||
def validate_data_type(cls, v):
|
def validate_data_type(cls, v):
|
||||||
if v not in ["image", "csv"]:
|
if v not in ["image", "csv"]:
|
||||||
raise ValueError("data_type must be image or csv")
|
raise ValueError("data_type must be image or csv")
|
||||||
|
|
@ -149,9 +151,7 @@ class StreamData(BaseModel):
|
||||||
data: dict
|
data: dict
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return (
|
return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
||||||
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomComponentCode(BaseModel):
|
class CustomComponentCode(BaseModel):
|
||||||
|
|
@ -198,3 +198,7 @@ class Token(BaseModel):
|
||||||
access_token: str
|
access_token: str
|
||||||
refresh_token: str
|
refresh_token: str
|
||||||
token_type: str
|
token_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class ApiKeyCreateRequest(BaseModel):
|
||||||
|
api_key: str
|
||||||
|
|
|
||||||
198
src/backend/langflow/api/v1/store.py
Normal file
198
src/backend/langflow/api/v1/store.py
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
import warnings
|
||||||
|
from typing import Annotated, List, Optional, Union
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
|
from langflow.services.auth import utils as auth_utils
|
||||||
|
from langflow.services.database.models.user.user import User
|
||||||
|
from langflow.services.deps import get_settings_service, get_store_service
|
||||||
|
from langflow.services.store.exceptions import CustomException
|
||||||
|
from langflow.services.store.schema import (
|
||||||
|
CreateComponentResponse,
|
||||||
|
DownloadComponentResponse,
|
||||||
|
ListComponentResponseModel,
|
||||||
|
StoreComponentCreate,
|
||||||
|
TagResponse,
|
||||||
|
UsersLikesResponse,
|
||||||
|
)
|
||||||
|
from langflow.services.store.service import StoreService
|
||||||
|
from langflow.services.store.utils import get_lf_version_from_pypi
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/store", tags=["Components Store"])
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_store_api_key(
|
||||||
|
user: User = Depends(auth_utils.get_current_active_user),
|
||||||
|
settings_service=Depends(get_settings_service),
|
||||||
|
):
|
||||||
|
if not user.store_api_key:
|
||||||
|
raise HTTPException(status_code=400, detail="You must have a store API key set.")
|
||||||
|
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||||
|
return decrypted
|
||||||
|
|
||||||
|
|
||||||
|
def get_optional_user_store_api_key(
|
||||||
|
user: User = Depends(auth_utils.get_current_active_user),
|
||||||
|
settings_service=Depends(get_settings_service),
|
||||||
|
):
|
||||||
|
if not user.store_api_key:
|
||||||
|
return None
|
||||||
|
decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service)
|
||||||
|
return decrypted
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/check/")
|
||||||
|
def check_if_store_is_enabled(
|
||||||
|
settings_service=Depends(get_settings_service),
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"enabled": settings_service.settings.STORE,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/check/api_key")
|
||||||
|
async def check_if_store_has_api_key(
|
||||||
|
api_key: Optional[str] = Depends(get_optional_user_store_api_key),
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
):
|
||||||
|
if api_key is None:
|
||||||
|
return {"has_api_key": False, "is_valid": False}
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_valid = await store_service.check_api_key(api_key)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
|
return {"has_api_key": api_key is not None, "is_valid": is_valid}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/components/", response_model=CreateComponentResponse, status_code=201)
|
||||||
|
async def share_component(
|
||||||
|
component: StoreComponentCreate,
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
store_api_Key: str = Depends(get_user_store_api_key),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
# Verify if this is the latest version of Langflow
|
||||||
|
# If not, raise an error
|
||||||
|
if not component.last_tested_version:
|
||||||
|
# Get the local version of Langflow
|
||||||
|
from langflow import __version__ as current_version
|
||||||
|
|
||||||
|
component.last_tested_version = current_version
|
||||||
|
langflow_version = get_lf_version_from_pypi()
|
||||||
|
if langflow_version is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail="Unable to verify the latest version of Langflow",
|
||||||
|
)
|
||||||
|
elif langflow_version != component.last_tested_version:
|
||||||
|
# If the user is using an older version of Langflow, we need to raise an error
|
||||||
|
# raise ValueError(
|
||||||
|
warnings.warn(
|
||||||
|
f"Your version of Langflow ({component.last_tested_version}) is outdated."
|
||||||
|
f" Please update to the latest version ({langflow_version}) and try again."
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await store_service.upload(store_api_Key, component)
|
||||||
|
return result
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/components/", response_model=ListComponentResponseModel)
|
||||||
|
async def get_components(
|
||||||
|
component_id: Annotated[Optional[str], Query()] = None,
|
||||||
|
search: Annotated[Optional[str], Query()] = None,
|
||||||
|
private: Annotated[Optional[bool], Query()] = None,
|
||||||
|
is_component: Annotated[Optional[bool], Query()] = None,
|
||||||
|
tags: Annotated[Optional[list[str]], Query()] = None,
|
||||||
|
sort: Annotated[Union[list[str], None], Query()] = None,
|
||||||
|
liked: Annotated[bool, Query()] = False,
|
||||||
|
filter_by_user: Annotated[bool, Query()] = False,
|
||||||
|
fields: Annotated[Optional[list[str]], Query()] = None,
|
||||||
|
page: int = 1,
|
||||||
|
limit: int = 10,
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
store_api_Key: Optional[str] = Depends(get_optional_user_store_api_key),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await store_service.get_list_component_response_model(
|
||||||
|
component_id=component_id,
|
||||||
|
search=search,
|
||||||
|
private=private,
|
||||||
|
is_component=is_component,
|
||||||
|
fields=fields,
|
||||||
|
tags=tags,
|
||||||
|
sort=sort,
|
||||||
|
liked=liked,
|
||||||
|
filter_by_user=filter_by_user,
|
||||||
|
page=page,
|
||||||
|
limit=limit,
|
||||||
|
store_api_key=store_api_Key,
|
||||||
|
)
|
||||||
|
except CustomException as exc:
|
||||||
|
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/components/{component_id}", response_model=DownloadComponentResponse)
|
||||||
|
async def download_component(
|
||||||
|
component_id: UUID,
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
store_api_Key: str = Depends(get_user_store_api_key),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
component = await store_service.download(store_api_Key, component_id)
|
||||||
|
except CustomException as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
if component is None:
|
||||||
|
raise HTTPException(status_code=400, detail="Component not found")
|
||||||
|
|
||||||
|
return component
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/tags", response_model=List[TagResponse])
|
||||||
|
async def get_tags(
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await store_service.get_tags()
|
||||||
|
except CustomException as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/users/likes", response_model=List[UsersLikesResponse])
|
||||||
|
async def get_list_of_components_liked_by_user(
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
store_api_Key: str = Depends(get_user_store_api_key),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return await store_service.get_user_likes(store_api_Key)
|
||||||
|
except CustomException as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/users/likes/{component_id}", response_model=UsersLikesResponse)
|
||||||
|
async def like_component(
|
||||||
|
component_id: UUID,
|
||||||
|
store_service: StoreService = Depends(get_store_service),
|
||||||
|
store_api_Key: str = Depends(get_user_store_api_key),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
result = await store_service.like_component(store_api_Key, str(component_id))
|
||||||
|
likes_count = await store_service.get_component_likes_count(str(component_id), store_api_Key)
|
||||||
|
|
||||||
|
return UsersLikesResponse(likes_count=likes_count, liked_by_user=result)
|
||||||
|
except CustomException as exc:
|
||||||
|
raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
|
|
@ -13,7 +13,7 @@ from sqlalchemy.exc import IntegrityError
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
|
||||||
from langflow.services.getters import get_session, get_settings_service
|
from langflow.services.deps import get_session, get_settings_service
|
||||||
from langflow.services.auth.utils import (
|
from langflow.services.auth.utils import (
|
||||||
get_current_active_superuser,
|
get_current_active_superuser,
|
||||||
get_current_active_user,
|
get_current_active_user,
|
||||||
|
|
@ -46,9 +46,7 @@ def add_user(
|
||||||
session.refresh(new_user)
|
session.refresh(new_user)
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="This username is unavailable.") from e
|
||||||
status_code=400, detail="This username is unavailable."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
return new_user
|
return new_user
|
||||||
|
|
||||||
|
|
@ -96,14 +94,10 @@ def patch_user(
|
||||||
Update an existing user's data.
|
Update an existing user's data.
|
||||||
"""
|
"""
|
||||||
if not user.is_superuser and user.id != user_id:
|
if not user.is_superuser and user.id != user_id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=403, detail="You don't have the permission to update this user")
|
||||||
status_code=403, detail="You don't have the permission to update this user"
|
|
||||||
)
|
|
||||||
if user_update.password:
|
if user_update.password:
|
||||||
if not user.is_superuser:
|
if not user.is_superuser:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't change your password here")
|
||||||
status_code=400, detail="You can't change your password here"
|
|
||||||
)
|
|
||||||
user_update.password = get_password_hash(user_update.password)
|
user_update.password = get_password_hash(user_update.password)
|
||||||
|
|
||||||
if user_db := get_user_by_id(session, user_id):
|
if user_db := get_user_by_id(session, user_id):
|
||||||
|
|
@ -123,16 +117,12 @@ def reset_password(
|
||||||
Reset a user's password.
|
Reset a user's password.
|
||||||
"""
|
"""
|
||||||
if user_id != user.id:
|
if user_id != user.id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't change another user's password")
|
||||||
status_code=400, detail="You can't change another user's password"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=404, detail="User not found")
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
if verify_password(user_update.password, user.password):
|
if verify_password(user_update.password, user.password):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't use your current password")
|
||||||
status_code=400, detail="You can't use your current password"
|
|
||||||
)
|
|
||||||
new_password = get_password_hash(user_update.password)
|
new_password = get_password_hash(user_update.password)
|
||||||
user.password = new_password
|
user.password = new_password
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
@ -151,13 +141,9 @@ def delete_user(
|
||||||
Delete a user from the database.
|
Delete a user from the database.
|
||||||
"""
|
"""
|
||||||
if current_user.id == user_id:
|
if current_user.id == user_id:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=400, detail="You can't delete your own user account")
|
||||||
status_code=400, detail="You can't delete your own user account"
|
|
||||||
)
|
|
||||||
elif not current_user.is_superuser:
|
elif not current_user.is_superuser:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=403, detail="You don't have the permission to delete this user")
|
||||||
status_code=403, detail="You don't have the permission to delete this user"
|
|
||||||
)
|
|
||||||
|
|
||||||
user_db = session.query(User).filter(User.id == user_id).first()
|
user_db = session.query(User).filter(User.id == user_id).first()
|
||||||
if not user_db:
|
if not user_db:
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
|
|
||||||
add_new_variables_to_template(input_variables, prompt_request)
|
add_new_variables_to_template(input_variables, prompt_request)
|
||||||
|
|
||||||
remove_old_variables_from_template(
|
remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request)
|
||||||
old_custom_fields, input_variables, prompt_request
|
|
||||||
)
|
|
||||||
|
|
||||||
update_input_variables_field(input_variables, prompt_request)
|
update_input_variables_field(input_variables, prompt_request)
|
||||||
|
|
||||||
|
|
@ -58,19 +56,12 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
|
|
||||||
def get_old_custom_fields(prompt_request):
|
def get_old_custom_fields(prompt_request):
|
||||||
try:
|
try:
|
||||||
if (
|
if len(prompt_request.frontend_node.custom_fields) == 1 and prompt_request.name == "":
|
||||||
len(prompt_request.frontend_node.custom_fields) == 1
|
|
||||||
and prompt_request.name == ""
|
|
||||||
):
|
|
||||||
# If there is only one custom field and the name is empty string
|
# If there is only one custom field and the name is empty string
|
||||||
# then we are dealing with the first prompt request after the node was created
|
# then we are dealing with the first prompt request after the node was created
|
||||||
prompt_request.name = list(
|
prompt_request.name = list(prompt_request.frontend_node.custom_fields.keys())[0]
|
||||||
prompt_request.frontend_node.custom_fields.keys()
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
old_custom_fields = prompt_request.frontend_node.custom_fields[
|
old_custom_fields = prompt_request.frontend_node.custom_fields[prompt_request.name].copy()
|
||||||
prompt_request.name
|
|
||||||
].copy()
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
old_custom_fields = []
|
old_custom_fields = []
|
||||||
prompt_request.frontend_node.custom_fields[prompt_request.name] = []
|
prompt_request.frontend_node.custom_fields[prompt_request.name] = []
|
||||||
|
|
@ -92,40 +83,26 @@ def add_new_variables_to_template(input_variables, prompt_request):
|
||||||
)
|
)
|
||||||
if variable in prompt_request.frontend_node.template:
|
if variable in prompt_request.frontend_node.template:
|
||||||
# Set the new field with the old value
|
# Set the new field with the old value
|
||||||
template_field.value = prompt_request.frontend_node.template[variable][
|
template_field.value = prompt_request.frontend_node.template[variable]["value"]
|
||||||
"value"
|
|
||||||
]
|
|
||||||
|
|
||||||
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
||||||
|
|
||||||
# Check if variable is not already in the list before appending
|
# Check if variable is not already in the list before appending
|
||||||
if (
|
if variable not in prompt_request.frontend_node.custom_fields[prompt_request.name]:
|
||||||
variable
|
prompt_request.frontend_node.custom_fields[prompt_request.name].append(variable)
|
||||||
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
|
||||||
):
|
|
||||||
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
|
|
||||||
variable
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
def remove_old_variables_from_template(
|
def remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request):
|
||||||
old_custom_fields, input_variables, prompt_request
|
|
||||||
):
|
|
||||||
for variable in old_custom_fields:
|
for variable in old_custom_fields:
|
||||||
if variable not in input_variables:
|
if variable not in input_variables:
|
||||||
try:
|
try:
|
||||||
# Remove the variable from custom_fields associated with the given name
|
# Remove the variable from custom_fields associated with the given name
|
||||||
if (
|
if variable in prompt_request.frontend_node.custom_fields[prompt_request.name]:
|
||||||
variable
|
prompt_request.frontend_node.custom_fields[prompt_request.name].remove(variable)
|
||||||
in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
|
||||||
):
|
|
||||||
prompt_request.frontend_node.custom_fields[
|
|
||||||
prompt_request.name
|
|
||||||
].remove(variable)
|
|
||||||
|
|
||||||
# Remove the variable from the template
|
# Remove the variable from the template
|
||||||
prompt_request.frontend_node.template.pop(variable, None)
|
prompt_request.frontend_node.template.pop(variable, None)
|
||||||
|
|
@ -137,6 +114,4 @@ def remove_old_variables_from_template(
|
||||||
|
|
||||||
def update_input_variables_field(input_variables, prompt_request):
|
def update_input_variables_field(input_variables, prompt_request):
|
||||||
if "input_variables" in prompt_request.frontend_node.template:
|
if "input_variables" in prompt_request.frontend_node.template:
|
||||||
prompt_request.frontend_node.template["input_variables"][
|
prompt_request.frontend_node.template["input_variables"]["value"] = input_variables
|
||||||
"value"
|
|
||||||
] = input_variables
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
from langflow import CustomComponent
|
from typing import List, Optional
|
||||||
from typing import Optional
|
|
||||||
from langchain.prompts import SystemMessagePromptTemplate
|
|
||||||
from langchain.tools import Tool
|
|
||||||
from langchain.schema.memory import BaseMemory
|
|
||||||
from langchain.chat_models import ChatOpenAI
|
|
||||||
|
|
||||||
from langchain.agents.agent import AgentExecutor
|
from langchain.agents.agent import AgentExecutor
|
||||||
|
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import _get_default_system_message
|
||||||
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
||||||
|
from langchain.chat_models import ChatOpenAI
|
||||||
from langchain.memory.token_buffer import ConversationTokenBufferMemory
|
from langchain.memory.token_buffer import ConversationTokenBufferMemory
|
||||||
|
from langchain.prompts import SystemMessagePromptTemplate
|
||||||
from langchain.prompts.chat import MessagesPlaceholder
|
from langchain.prompts.chat import MessagesPlaceholder
|
||||||
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import (
|
from langchain.schema.memory import BaseMemory
|
||||||
_get_default_system_message,
|
from langchain.tools import Tool
|
||||||
)
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
class ConversationalAgent(CustomComponent):
|
class ConversationalAgent(CustomComponent):
|
||||||
|
|
@ -20,13 +18,14 @@ class ConversationalAgent(CustomComponent):
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
openai_function_models = [
|
openai_function_models = [
|
||||||
"gpt-3.5-turbo-0613",
|
"gpt-4-1106-preview",
|
||||||
"gpt-3.5-turbo-16k-0613",
|
"gpt-3.5-turbo",
|
||||||
"gpt-4-0613",
|
"gpt-3.5-turbo-16k",
|
||||||
"gpt-4-32k-0613",
|
"gpt-4",
|
||||||
|
"gpt-4-32k",
|
||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
"tools": {"is_list": True, "display_name": "Tools"},
|
"tools": {"display_name": "Tools"},
|
||||||
"memory": {"display_name": "Memory"},
|
"memory": {"display_name": "Memory"},
|
||||||
"system_message": {"display_name": "System Message"},
|
"system_message": {"display_name": "System Message"},
|
||||||
"max_token_limit": {"display_name": "Max Token Limit"},
|
"max_token_limit": {"display_name": "Max Token Limit"},
|
||||||
|
|
@ -42,7 +41,7 @@ class ConversationalAgent(CustomComponent):
|
||||||
self,
|
self,
|
||||||
model_name: str,
|
model_name: str,
|
||||||
openai_api_key: str,
|
openai_api_key: str,
|
||||||
tools: Tool,
|
tools: List[Tool],
|
||||||
openai_api_base: Optional[str] = None,
|
openai_api_base: Optional[str] = None,
|
||||||
memory: Optional[BaseMemory] = None,
|
memory: Optional[BaseMemory] = None,
|
||||||
system_message: Optional[SystemMessagePromptTemplate] = None,
|
system_message: Optional[SystemMessagePromptTemplate] = None,
|
||||||
|
|
@ -50,8 +49,8 @@ class ConversationalAgent(CustomComponent):
|
||||||
) -> AgentExecutor:
|
) -> AgentExecutor:
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(
|
||||||
model=model_name,
|
model=model_name,
|
||||||
openai_api_key=openai_api_key,
|
api_key=openai_api_key,
|
||||||
openai_api_base=openai_api_base,
|
base_url=openai_api_base,
|
||||||
)
|
)
|
||||||
if not memory:
|
if not memory:
|
||||||
memory_key = "chat_history"
|
memory_key = "chat_history"
|
||||||
|
|
@ -71,7 +70,9 @@ class ConversationalAgent(CustomComponent):
|
||||||
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
|
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
|
||||||
)
|
)
|
||||||
agent = OpenAIFunctionsAgent(
|
agent = OpenAIFunctionsAgent(
|
||||||
llm=llm, tools=tools, prompt=prompt # type: ignore
|
llm=llm,
|
||||||
|
tools=tools,
|
||||||
|
prompt=prompt, # type: ignore
|
||||||
)
|
)
|
||||||
return AgentExecutor(
|
return AgentExecutor(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
|
|
@ -79,4 +80,5 @@ class ConversationalAgent(CustomComponent):
|
||||||
memory=memory,
|
memory=memory,
|
||||||
verbose=True,
|
verbose=True,
|
||||||
return_intermediate_steps=True,
|
return_intermediate_steps=True,
|
||||||
|
handle_parsing_errors=True,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
29
src/backend/langflow/components/chains/ConversationChain.py
Normal file
29
src/backend/langflow/components/chains/ConversationChain.py
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.chains import ConversationChain
|
||||||
|
from typing import Optional, Union, Callable
|
||||||
|
from langflow.field_typing import BaseLanguageModel, BaseMemory, Chain
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationChainComponent(CustomComponent):
|
||||||
|
display_name = "ConversationChain"
|
||||||
|
description = "Chain to have a conversation and load context from memory."
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"prompt": {"display_name": "Prompt"},
|
||||||
|
"llm": {"display_name": "LLM"},
|
||||||
|
"memory": {
|
||||||
|
"display_name": "Memory",
|
||||||
|
"info": "Memory to load context from. If none is provided, a ConversationBufferMemory will be used.",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
llm: BaseLanguageModel,
|
||||||
|
memory: Optional[BaseMemory] = None,
|
||||||
|
) -> Union[Chain, Callable]:
|
||||||
|
if memory is None:
|
||||||
|
return ConversationChain(llm=llm)
|
||||||
|
return ConversationChain(llm=llm, memory=memory)
|
||||||
30
src/backend/langflow/components/chains/LLMChain.py
Normal file
30
src/backend/langflow/components/chains/LLMChain.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.chains import LLMChain
|
||||||
|
from typing import Optional, Union, Callable
|
||||||
|
from langflow.field_typing import (
|
||||||
|
BasePromptTemplate,
|
||||||
|
BaseLanguageModel,
|
||||||
|
BaseMemory,
|
||||||
|
Chain,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LLMChainComponent(CustomComponent):
|
||||||
|
display_name = "LLMChain"
|
||||||
|
description = "Chain to run queries against LLMs"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"prompt": {"display_name": "Prompt"},
|
||||||
|
"llm": {"display_name": "LLM"},
|
||||||
|
"memory": {"display_name": "Memory"},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
prompt: BasePromptTemplate,
|
||||||
|
llm: BaseLanguageModel,
|
||||||
|
memory: Optional[BaseMemory] = None,
|
||||||
|
) -> Union[Chain, Callable]:
|
||||||
|
return LLMChain(prompt=prompt, llm=llm, memory=memory)
|
||||||
|
|
@ -8,7 +8,7 @@ from langchain.schema import Document
|
||||||
class PromptRunner(CustomComponent):
|
class PromptRunner(CustomComponent):
|
||||||
display_name: str = "Prompt Runner"
|
display_name: str = "Prompt Runner"
|
||||||
description: str = "Run a Chain with the given PromptTemplate"
|
description: str = "Run a Chain with the given PromptTemplate"
|
||||||
beta = True
|
beta: bool = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"llm": {"display_name": "LLM"},
|
"llm": {"display_name": "LLM"},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
|
|
@ -18,9 +18,7 @@ class PromptRunner(CustomComponent):
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}) -> Document:
|
||||||
self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}
|
|
||||||
) -> Document:
|
|
||||||
chain = prompt | llm
|
chain = prompt | llm
|
||||||
# The input is an empty dict because the prompt is already filled
|
# The input is an empty dict because the prompt is already filled
|
||||||
result = chain.invoke(input=inputs)
|
result = chain.invoke(input=inputs)
|
||||||
|
|
|
||||||
114
src/backend/langflow/components/documentloaders/FileLoader.py
Normal file
114
src/backend/langflow/components/documentloaders/FileLoader.py
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
from langchain.schema import Document
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.utils.constants import LOADERS_INFO
|
||||||
|
|
||||||
|
|
||||||
|
class FileLoaderComponent(CustomComponent):
|
||||||
|
display_name: str = "File Loader"
|
||||||
|
description: str = "Generic File Loader"
|
||||||
|
beta = True
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
loader_options = ["Automatic"] + [loader_info["name"] for loader_info in LOADERS_INFO]
|
||||||
|
|
||||||
|
file_types = []
|
||||||
|
suffixes = []
|
||||||
|
|
||||||
|
for loader_info in LOADERS_INFO:
|
||||||
|
if "allowedTypes" in loader_info:
|
||||||
|
file_types.extend(loader_info["allowedTypes"])
|
||||||
|
suffixes.extend([f".{ext}" for ext in loader_info["allowedTypes"]])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"file_path": {
|
||||||
|
"display_name": "File Path",
|
||||||
|
"required": True,
|
||||||
|
"field_type": "file",
|
||||||
|
"file_types": [
|
||||||
|
"json",
|
||||||
|
"txt",
|
||||||
|
"csv",
|
||||||
|
"jsonl",
|
||||||
|
"html",
|
||||||
|
"htm",
|
||||||
|
"conllu",
|
||||||
|
"enex",
|
||||||
|
"msg",
|
||||||
|
"pdf",
|
||||||
|
"srt",
|
||||||
|
"eml",
|
||||||
|
"md",
|
||||||
|
"pptx",
|
||||||
|
"docx",
|
||||||
|
],
|
||||||
|
"suffixes": [
|
||||||
|
".json",
|
||||||
|
".txt",
|
||||||
|
".csv",
|
||||||
|
".jsonl",
|
||||||
|
".html",
|
||||||
|
".htm",
|
||||||
|
".conllu",
|
||||||
|
".enex",
|
||||||
|
".msg",
|
||||||
|
".pdf",
|
||||||
|
".srt",
|
||||||
|
".eml",
|
||||||
|
".md",
|
||||||
|
".pptx",
|
||||||
|
".docx",
|
||||||
|
],
|
||||||
|
# "file_types" : file_types,
|
||||||
|
# "suffixes": suffixes,
|
||||||
|
},
|
||||||
|
"loader": {
|
||||||
|
"display_name": "Loader",
|
||||||
|
"is_list": True,
|
||||||
|
"required": True,
|
||||||
|
"options": loader_options,
|
||||||
|
"value": "Automatic",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, file_path: str, loader: str) -> Document:
|
||||||
|
file_type = file_path.split(".")[-1]
|
||||||
|
|
||||||
|
# Mapeie o nome do loader selecionado para suas informações
|
||||||
|
selected_loader_info = None
|
||||||
|
for loader_info in LOADERS_INFO:
|
||||||
|
if loader_info["name"] == loader:
|
||||||
|
selected_loader_info = loader_info
|
||||||
|
break
|
||||||
|
|
||||||
|
if selected_loader_info is None and loader != "Automatic":
|
||||||
|
raise ValueError(f"Loader {loader} not found in the loader info list")
|
||||||
|
|
||||||
|
if loader == "Automatic":
|
||||||
|
# Determine o loader automaticamente com base na extensão do arquivo
|
||||||
|
default_loader_info = None
|
||||||
|
for info in LOADERS_INFO:
|
||||||
|
if "defaultFor" in info and file_type in info["defaultFor"]:
|
||||||
|
default_loader_info = info
|
||||||
|
break
|
||||||
|
|
||||||
|
if default_loader_info is None:
|
||||||
|
raise ValueError(f"No default loader found for file type: {file_type}")
|
||||||
|
|
||||||
|
selected_loader_info = default_loader_info
|
||||||
|
if isinstance(selected_loader_info, dict):
|
||||||
|
loader_import: str = selected_loader_info["import"]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}")
|
||||||
|
module_name, class_name = loader_import.rsplit(".", 1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Importe o loader dinamicamente
|
||||||
|
loader_module = __import__(module_name, fromlist=[class_name])
|
||||||
|
loader_instance = getattr(loader_module, class_name)
|
||||||
|
except ImportError as e:
|
||||||
|
raise ValueError(f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}") from e
|
||||||
|
|
||||||
|
result = loader_instance(file_path=file_path)
|
||||||
|
return result.load()
|
||||||
62
src/backend/langflow/components/documentloaders/UrlLoader.py
Normal file
62
src/backend/langflow/components/documentloaders/UrlLoader.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
from typing import List
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.document_loaders import AZLyricsLoader
|
||||||
|
from langchain.document_loaders import CollegeConfidentialLoader
|
||||||
|
from langchain.document_loaders import GitbookLoader
|
||||||
|
from langchain.document_loaders import HNLoader
|
||||||
|
from langchain.document_loaders import IFixitLoader
|
||||||
|
from langchain.document_loaders import IMSDbLoader
|
||||||
|
from langchain.document_loaders import WebBaseLoader
|
||||||
|
|
||||||
|
|
||||||
|
from langchain.schema import Document
|
||||||
|
|
||||||
|
|
||||||
|
class UrlLoaderComponent(CustomComponent):
|
||||||
|
display_name: str = "Url Loader"
|
||||||
|
description: str = "Generic Url Loader Component"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"web_path": {
|
||||||
|
"display_name": "Url",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"loader": {
|
||||||
|
"display_name": "Loader",
|
||||||
|
"is_list": True,
|
||||||
|
"required": True,
|
||||||
|
"options": [
|
||||||
|
"AZLyricsLoader",
|
||||||
|
"CollegeConfidentialLoader",
|
||||||
|
"GitbookLoader",
|
||||||
|
"HNLoader",
|
||||||
|
"IFixitLoader",
|
||||||
|
"IMSDbLoader",
|
||||||
|
"WebBaseLoader",
|
||||||
|
],
|
||||||
|
"value": "WebBaseLoader",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, web_path: str, loader: str) -> List[Document]:
|
||||||
|
if loader == "AZLyricsLoader":
|
||||||
|
loader_instance = AZLyricsLoader(web_path=web_path) # type: ignore
|
||||||
|
elif loader == "CollegeConfidentialLoader":
|
||||||
|
loader_instance = CollegeConfidentialLoader(web_path=web_path) # type: ignore
|
||||||
|
elif loader == "GitbookLoader":
|
||||||
|
loader_instance = GitbookLoader(web_page=web_path) # type: ignore
|
||||||
|
elif loader == "HNLoader":
|
||||||
|
loader_instance = HNLoader(web_path=web_path) # type: ignore
|
||||||
|
elif loader == "IFixitLoader":
|
||||||
|
loader_instance = IFixitLoader(web_path=web_path) # type: ignore
|
||||||
|
elif loader == "IMSDbLoader":
|
||||||
|
loader_instance = IMSDbLoader(web_path=web_path) # type: ignore
|
||||||
|
elif loader == "WebBaseLoader":
|
||||||
|
loader_instance = WebBaseLoader(web_path=web_path) # type: ignore
|
||||||
|
|
||||||
|
if loader_instance is None:
|
||||||
|
raise ValueError(f"No loader found for: {web_path}")
|
||||||
|
|
||||||
|
return loader_instance.load()
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
from langchain.embeddings import BedrockEmbeddings
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
|
||||||
|
class AmazonBedrockEmeddingsComponent(CustomComponent):
|
||||||
|
"""
|
||||||
|
A custom component for implementing an Embeddings Model using Amazon Bedrock.
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name: str = "Amazon Bedrock Embeddings"
|
||||||
|
description: str = "Embeddings model from Amazon Bedrock."
|
||||||
|
documentation = "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/bedrock"
|
||||||
|
beta = True
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"model_id": {
|
||||||
|
"display_name": "Model Id",
|
||||||
|
"options": ["amazon.titan-embed-text-v1"],
|
||||||
|
},
|
||||||
|
"credentials_profile_name": {"display_name": "Credentials Profile Name"},
|
||||||
|
"endpoint_url": {"display_name": "Bedrock Endpoint URL"},
|
||||||
|
"region_name": {"display_name": "AWS Region"},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
model_id: str = "amazon.titan-embed-text-v1",
|
||||||
|
credentials_profile_name: Optional[str] = None,
|
||||||
|
endpoint_url: Optional[str] = None,
|
||||||
|
region_name: Optional[str] = None,
|
||||||
|
) -> Embeddings:
|
||||||
|
try:
|
||||||
|
output = BedrockEmbeddings(
|
||||||
|
credentials_profile_name=credentials_profile_name,
|
||||||
|
model_id=model_id,
|
||||||
|
endpoint_url=endpoint_url,
|
||||||
|
region_name=region_name,
|
||||||
|
) # type: ignore
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to AmazonBedrock API.") from e
|
||||||
|
return output
|
||||||
0
src/backend/langflow/components/embeddings/__init__.py
Normal file
0
src/backend/langflow/components/embeddings/__init__.py
Normal file
45
src/backend/langflow/components/llms/AmazonBedrock.py
Normal file
45
src/backend/langflow/components/llms/AmazonBedrock.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.llms.bedrock import Bedrock
|
||||||
|
from langchain.llms.base import BaseLLM
|
||||||
|
|
||||||
|
|
||||||
|
class AmazonBedrockComponent(CustomComponent):
|
||||||
|
display_name: str = "Amazon Bedrock"
|
||||||
|
description: str = "LLM model from Amazon Bedrock."
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"model_id": {
|
||||||
|
"display_name": "Model Id",
|
||||||
|
"options": [
|
||||||
|
"ai21.j2-grande-instruct",
|
||||||
|
"ai21.j2-jumbo-instruct",
|
||||||
|
"ai21.j2-mid",
|
||||||
|
"ai21.j2-mid-v1",
|
||||||
|
"ai21.j2-ultra",
|
||||||
|
"ai21.j2-ultra-v1",
|
||||||
|
"anthropic.claude-instant-v1",
|
||||||
|
"anthropic.claude-v1",
|
||||||
|
"anthropic.claude-v2",
|
||||||
|
"cohere.command-text-v14",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"credentials_profile_name": {"display_name": "Credentials Profile Name"},
|
||||||
|
"streaming": {"display_name": "Streaming", "field_type": "bool"},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
model_id: str = "anthropic.claude-instant-v1",
|
||||||
|
credentials_profile_name: Optional[str] = None,
|
||||||
|
) -> BaseLLM:
|
||||||
|
try:
|
||||||
|
output = Bedrock(
|
||||||
|
credentials_profile_name=credentials_profile_name,
|
||||||
|
model_id=model_id,
|
||||||
|
) # type: ignore
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to AmazonBedrock API.") from e
|
||||||
|
return output
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
|
||||||
|
from langchain.llms.base import BaseLLM
|
||||||
|
|
||||||
|
|
||||||
|
class QianfanChatEndpointComponent(CustomComponent):
|
||||||
|
display_name: str = "QianfanChatEndpoint"
|
||||||
|
description: str = (
|
||||||
|
"Baidu Qianfan chat models. Get more detail from "
|
||||||
|
"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint."
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"model": {
|
||||||
|
"display_name": "Model Name",
|
||||||
|
"options": [
|
||||||
|
"ERNIE-Bot",
|
||||||
|
"ERNIE-Bot-turbo",
|
||||||
|
"BLOOMZ-7B",
|
||||||
|
"Llama-2-7b-chat",
|
||||||
|
"Llama-2-13b-chat",
|
||||||
|
"Llama-2-70b-chat",
|
||||||
|
"Qianfan-BLOOMZ-7B-compressed",
|
||||||
|
"Qianfan-Chinese-Llama-2-7B",
|
||||||
|
"ChatGLM2-6B-32K",
|
||||||
|
"AquilaChat-7B",
|
||||||
|
],
|
||||||
|
"info": "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"qianfan_ak": {
|
||||||
|
"display_name": "Qianfan Ak",
|
||||||
|
"required": True,
|
||||||
|
"password": True,
|
||||||
|
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
|
||||||
|
},
|
||||||
|
"qianfan_sk": {
|
||||||
|
"display_name": "Qianfan Sk",
|
||||||
|
"required": True,
|
||||||
|
"password": True,
|
||||||
|
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
|
||||||
|
},
|
||||||
|
"top_p": {
|
||||||
|
"display_name": "Top p",
|
||||||
|
"field_type": "float",
|
||||||
|
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
|
||||||
|
"value": 0.8,
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"display_name": "Temperature",
|
||||||
|
"field_type": "float",
|
||||||
|
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
|
||||||
|
"value": 0.95,
|
||||||
|
},
|
||||||
|
"penalty_score": {
|
||||||
|
"display_name": "Penalty Score",
|
||||||
|
"field_type": "float",
|
||||||
|
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
|
||||||
|
"value": 1.0,
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"display_name": "Endpoint",
|
||||||
|
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
model: str = "ERNIE-Bot-turbo",
|
||||||
|
qianfan_ak: Optional[str] = None,
|
||||||
|
qianfan_sk: Optional[str] = None,
|
||||||
|
top_p: Optional[float] = None,
|
||||||
|
temperature: Optional[float] = None,
|
||||||
|
penalty_score: Optional[float] = None,
|
||||||
|
endpoint: Optional[str] = None,
|
||||||
|
) -> BaseLLM:
|
||||||
|
try:
|
||||||
|
output = QianfanChatEndpoint( # type: ignore
|
||||||
|
model=model,
|
||||||
|
qianfan_ak=qianfan_ak,
|
||||||
|
qianfan_sk=qianfan_sk,
|
||||||
|
top_p=top_p,
|
||||||
|
temperature=temperature,
|
||||||
|
penalty_score=penalty_score,
|
||||||
|
endpoint=endpoint,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to Baidu Qianfan API.") from e
|
||||||
|
return output # type: ignore
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.llms.baidu_qianfan_endpoint import QianfanLLMEndpoint
|
||||||
|
from langchain.llms.base import BaseLLM
|
||||||
|
|
||||||
|
|
||||||
|
class QianfanLLMEndpointComponent(CustomComponent):
|
||||||
|
display_name: str = "QianfanLLMEndpoint"
|
||||||
|
description: str = (
|
||||||
|
"Baidu Qianfan hosted open source or customized models. "
|
||||||
|
"Get more detail from https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint"
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"model": {
|
||||||
|
"display_name": "Model Name",
|
||||||
|
"options": [
|
||||||
|
"ERNIE-Bot",
|
||||||
|
"ERNIE-Bot-turbo",
|
||||||
|
"BLOOMZ-7B",
|
||||||
|
"Llama-2-7b-chat",
|
||||||
|
"Llama-2-13b-chat",
|
||||||
|
"Llama-2-70b-chat",
|
||||||
|
"Qianfan-BLOOMZ-7B-compressed",
|
||||||
|
"Qianfan-Chinese-Llama-2-7B",
|
||||||
|
"ChatGLM2-6B-32K",
|
||||||
|
"AquilaChat-7B",
|
||||||
|
],
|
||||||
|
"info": "https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"qianfan_ak": {
|
||||||
|
"display_name": "Qianfan Ak",
|
||||||
|
"required": True,
|
||||||
|
"password": True,
|
||||||
|
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
|
||||||
|
},
|
||||||
|
"qianfan_sk": {
|
||||||
|
"display_name": "Qianfan Sk",
|
||||||
|
"required": True,
|
||||||
|
"password": True,
|
||||||
|
"info": "which you could get from https://cloud.baidu.com/product/wenxinworkshop",
|
||||||
|
},
|
||||||
|
"top_p": {
|
||||||
|
"display_name": "Top p",
|
||||||
|
"field_type": "float",
|
||||||
|
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
|
||||||
|
"value": 0.8,
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"display_name": "Temperature",
|
||||||
|
"field_type": "float",
|
||||||
|
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
|
||||||
|
"value": 0.95,
|
||||||
|
},
|
||||||
|
"penalty_score": {
|
||||||
|
"display_name": "Penalty Score",
|
||||||
|
"field_type": "float",
|
||||||
|
"info": "Model params, only supported in ERNIE-Bot and ERNIE-Bot-turbo",
|
||||||
|
"value": 1.0,
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"display_name": "Endpoint",
|
||||||
|
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
model: str = "ERNIE-Bot-turbo",
|
||||||
|
qianfan_ak: Optional[str] = None,
|
||||||
|
qianfan_sk: Optional[str] = None,
|
||||||
|
top_p: Optional[float] = None,
|
||||||
|
temperature: Optional[float] = None,
|
||||||
|
penalty_score: Optional[float] = None,
|
||||||
|
endpoint: Optional[str] = None,
|
||||||
|
) -> BaseLLM:
|
||||||
|
try:
|
||||||
|
output = QianfanLLMEndpoint( # type: ignore
|
||||||
|
model=model,
|
||||||
|
qianfan_ak=qianfan_ak,
|
||||||
|
qianfan_sk=qianfan_sk,
|
||||||
|
top_p=top_p,
|
||||||
|
temperature=temperature,
|
||||||
|
penalty_score=penalty_score,
|
||||||
|
endpoint=endpoint,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to Baidu Qianfan API.") from e
|
||||||
|
return output # type: ignore
|
||||||
48
src/backend/langflow/components/retrievers/AmazonKendra.py
Normal file
48
src/backend/langflow/components/retrievers/AmazonKendra.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langchain.retrievers import AmazonKendraRetriever
|
||||||
|
from langchain.schema import BaseRetriever
|
||||||
|
|
||||||
|
|
||||||
|
class AmazonKendraRetrieverComponent(CustomComponent):
|
||||||
|
display_name: str = "Amazon Kendra Retriever"
|
||||||
|
description: str = "Retriever that uses the Amazon Kendra API."
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"index_id": {"display_name": "Index ID"},
|
||||||
|
"region_name": {"display_name": "Region Name"},
|
||||||
|
"credentials_profile_name": {"display_name": "Credentials Profile Name"},
|
||||||
|
"attribute_filter": {
|
||||||
|
"display_name": "Attribute Filter",
|
||||||
|
"field_type": "code",
|
||||||
|
},
|
||||||
|
"top_k": {"display_name": "Top K", "field_type": "int"},
|
||||||
|
"user_context": {
|
||||||
|
"display_name": "User Context",
|
||||||
|
"field_type": "code",
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
index_id: str,
|
||||||
|
top_k: int = 3,
|
||||||
|
region_name: Optional[str] = None,
|
||||||
|
credentials_profile_name: Optional[str] = None,
|
||||||
|
attribute_filter: Optional[dict] = None,
|
||||||
|
user_context: Optional[dict] = None,
|
||||||
|
) -> BaseRetriever:
|
||||||
|
try:
|
||||||
|
output = AmazonKendraRetriever(
|
||||||
|
index_id=index_id,
|
||||||
|
top_k=top_k,
|
||||||
|
region_name=region_name,
|
||||||
|
credentials_profile_name=credentials_profile_name,
|
||||||
|
attribute_filter=attribute_filter,
|
||||||
|
user_context=user_context,
|
||||||
|
) # type: ignore
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not connect to AmazonKendra API.") from e
|
||||||
|
return output
|
||||||
|
|
@ -18,9 +18,7 @@ class MetalRetrieverComponent(CustomComponent):
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None) -> BaseRetriever:
|
||||||
self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None
|
|
||||||
) -> BaseRetriever:
|
|
||||||
try:
|
try:
|
||||||
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
|
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
from typing import List, Union
|
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 import tool
|
||||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
|
from langchain.tools import Tool
|
||||||
|
from metaphor_python import Metaphor # type: ignore
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
class MetaphorToolkit(CustomComponent):
|
class MetaphorToolkit(CustomComponent):
|
||||||
display_name: str = "Metaphor"
|
display_name: str = "Metaphor"
|
||||||
description: str = "Metaphor Toolkit"
|
description: str = "Metaphor Toolkit"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/tools/metaphor_search"
|
||||||
"https://python.langchain.com/docs/integrations/tools/metaphor_search"
|
beta: bool = True
|
||||||
)
|
|
||||||
beta = True
|
|
||||||
# api key should be password = True
|
# api key should be password = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"metaphor_api_key": {"display_name": "Metaphor API Key", "password": True},
|
"metaphor_api_key": {"display_name": "Metaphor API Key", "password": True},
|
||||||
|
|
@ -33,9 +32,7 @@ class MetaphorToolkit(CustomComponent):
|
||||||
@tool
|
@tool
|
||||||
def search(query: str):
|
def search(query: str):
|
||||||
"""Call search engine with a query."""
|
"""Call search engine with a query."""
|
||||||
return client.search(
|
return client.search(query, use_autoprompt=use_autoprompt, num_results=search_num_results)
|
||||||
query, use_autoprompt=use_autoprompt, num_results=search_num_results
|
|
||||||
)
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def get_contents(ids: List[str]):
|
def get_contents(ids: List[str]):
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class GetRequest(CustomComponent):
|
||||||
description: str = "Make a GET request to the given URL."
|
description: str = "Make a GET request to the given URL."
|
||||||
output_types: list[str] = ["Document"]
|
output_types: list[str] = ["Document"]
|
||||||
documentation: str = "https://docs.langflow.org/components/utilities#get-request"
|
documentation: str = "https://docs.langflow.org/components/utilities#get-request"
|
||||||
beta = True
|
beta: bool = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"url": {
|
"url": {
|
||||||
"display_name": "URL",
|
"display_name": "URL",
|
||||||
|
|
@ -30,9 +30,7 @@ class GetRequest(CustomComponent):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_document(
|
def get_document(self, session: requests.Session, url: str, headers: Optional[dict], timeout: int) -> Document:
|
||||||
self, session: requests.Session, url: str, headers: Optional[dict], timeout: int
|
|
||||||
) -> Document:
|
|
||||||
try:
|
try:
|
||||||
response = session.get(url, headers=headers, timeout=int(timeout))
|
response = session.get(url, headers=headers, timeout=int(timeout))
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
|
|
||||||
# - **Document:** The Document containing the JSON object.
|
# - **Document:** The Document containing the JSON object.
|
||||||
|
|
||||||
from langflow import CustomComponent
|
|
||||||
from langchain.schema import Document
|
from langchain.schema import Document
|
||||||
|
from langflow import CustomComponent
|
||||||
from langflow.services.database.models.base import orjson_dumps
|
from langflow.services.database.models.base import orjson_dumps
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -21,9 +21,7 @@ class JSONDocumentBuilder(CustomComponent):
|
||||||
description: str = "Build a Document containing a JSON object using a key and another Document page content."
|
description: str = "Build a Document containing a JSON object using a key and another Document page content."
|
||||||
output_types: list[str] = ["Document"]
|
output_types: list[str] = ["Document"]
|
||||||
beta = True
|
beta = True
|
||||||
documentation: str = (
|
documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder"
|
||||||
"https://docs.langflow.org/components/utilities#json-document-builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
field_config = {
|
field_config = {
|
||||||
"key": {"display_name": "Key"},
|
"key": {"display_name": "Key"},
|
||||||
|
|
@ -38,18 +36,11 @@ class JSONDocumentBuilder(CustomComponent):
|
||||||
documents = None
|
documents = None
|
||||||
if isinstance(document, list):
|
if isinstance(document, list):
|
||||||
documents = [
|
documents = [
|
||||||
Document(
|
Document(page_content=orjson_dumps({key: doc.page_content}, indent_2=False)) for doc in document
|
||||||
page_content=orjson_dumps({key: doc.page_content}, indent_2=False)
|
|
||||||
)
|
|
||||||
for doc in document
|
|
||||||
]
|
]
|
||||||
elif isinstance(document, Document):
|
elif isinstance(document, Document):
|
||||||
documents = Document(
|
documents = Document(page_content=orjson_dumps({key: document.page_content}, indent_2=False))
|
||||||
page_content=orjson_dumps({key: document.page_content}, indent_2=False)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(f"Expected Document or list of Documents, got {type(document)}")
|
||||||
f"Expected Document or list of Documents, got {type(document)}"
|
|
||||||
)
|
|
||||||
self.repr_value = documents
|
self.repr_value = documents
|
||||||
return documents
|
return documents
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class PostRequest(CustomComponent):
|
||||||
description: str = "Make a POST request to the given URL."
|
description: str = "Make a POST request to the given URL."
|
||||||
output_types: list[str] = ["Document"]
|
output_types: list[str] = ["Document"]
|
||||||
documentation: str = "https://docs.langflow.org/components/utilities#post-request"
|
documentation: str = "https://docs.langflow.org/components/utilities#post-request"
|
||||||
beta = True
|
beta: bool = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"url": {"display_name": "URL", "info": "The URL to make the request to."},
|
"url": {"display_name": "URL", "info": "The URL to make the request to."},
|
||||||
"headers": {
|
"headers": {
|
||||||
|
|
@ -65,16 +65,12 @@ class PostRequest(CustomComponent):
|
||||||
|
|
||||||
if not isinstance(document, list) and isinstance(document, Document):
|
if not isinstance(document, list) and isinstance(document, Document):
|
||||||
documents: list[Document] = [document]
|
documents: list[Document] = [document]
|
||||||
elif isinstance(document, list) and all(
|
elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document):
|
||||||
isinstance(doc, Document) for doc in document
|
|
||||||
):
|
|
||||||
documents = document
|
documents = document
|
||||||
else:
|
else:
|
||||||
raise ValueError("document must be a Document or a list of Documents")
|
raise ValueError("document must be a Document or a list of Documents")
|
||||||
|
|
||||||
with requests.Session() as session:
|
with requests.Session() as session:
|
||||||
documents = [
|
documents = [self.post_document(session, doc, url, headers) for doc in documents]
|
||||||
self.post_document(session, doc, url, headers) for doc in documents
|
|
||||||
]
|
|
||||||
self.repr_value = documents
|
self.repr_value = documents
|
||||||
return documents
|
return documents
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class UpdateRequest(CustomComponent):
|
||||||
description: str = "Make a PATCH request to the given URL."
|
description: str = "Make a PATCH request to the given URL."
|
||||||
output_types: list[str] = ["Document"]
|
output_types: list[str] = ["Document"]
|
||||||
documentation: str = "https://docs.langflow.org/components/utilities#update-request"
|
documentation: str = "https://docs.langflow.org/components/utilities#update-request"
|
||||||
beta = True
|
beta: bool = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"url": {"display_name": "URL", "info": "The URL to make the request to."},
|
"url": {"display_name": "URL", "info": "The URL to make the request to."},
|
||||||
"headers": {
|
"headers": {
|
||||||
|
|
@ -39,9 +39,7 @@ class UpdateRequest(CustomComponent):
|
||||||
) -> Document:
|
) -> Document:
|
||||||
try:
|
try:
|
||||||
if method == "PATCH":
|
if method == "PATCH":
|
||||||
response = session.patch(
|
response = session.patch(url, headers=headers, data=document.page_content)
|
||||||
url, headers=headers, data=document.page_content
|
|
||||||
)
|
|
||||||
elif method == "PUT":
|
elif method == "PUT":
|
||||||
response = session.put(url, headers=headers, data=document.page_content)
|
response = session.put(url, headers=headers, data=document.page_content)
|
||||||
else:
|
else:
|
||||||
|
|
@ -78,17 +76,12 @@ class UpdateRequest(CustomComponent):
|
||||||
|
|
||||||
if not isinstance(document, list) and isinstance(document, Document):
|
if not isinstance(document, list) and isinstance(document, Document):
|
||||||
documents: list[Document] = [document]
|
documents: list[Document] = [document]
|
||||||
elif isinstance(document, list) and all(
|
elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document):
|
||||||
isinstance(doc, Document) for doc in document
|
|
||||||
):
|
|
||||||
documents = document
|
documents = document
|
||||||
else:
|
else:
|
||||||
raise ValueError("document must be a Document or a list of Documents")
|
raise ValueError("document must be a Document or a list of Documents")
|
||||||
|
|
||||||
with requests.Session() as session:
|
with requests.Session() as session:
|
||||||
documents = [
|
documents = [self.update_document(session, doc, url, headers, method) for doc in documents]
|
||||||
self.update_document(session, doc, url, headers, method)
|
|
||||||
for doc in documents
|
|
||||||
]
|
|
||||||
self.repr_value = documents
|
self.repr_value = documents
|
||||||
return documents
|
return documents
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ class ChromaComponent(CustomComponent):
|
||||||
A custom component for implementing a Vector Store using Chroma.
|
A custom component for implementing a Vector Store using Chroma.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
display_name: str = "Chroma (Custom Component)"
|
display_name: str = "Chroma"
|
||||||
description: str = "Implementation of Vector Store using Chroma"
|
description: str = "Implementation of Vector Store using Chroma"
|
||||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
|
||||||
beta = True
|
beta: bool = True
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -86,8 +86,7 @@ class ChromaComponent(CustomComponent):
|
||||||
|
|
||||||
if chroma_server_host is not None:
|
if chroma_server_host is not None:
|
||||||
chroma_settings = chromadb.config.Settings(
|
chroma_settings = chromadb.config.Settings(
|
||||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
|
||||||
or None,
|
|
||||||
chroma_server_host=chroma_server_host,
|
chroma_server_host=chroma_server_host,
|
||||||
chroma_server_port=chroma_server_port or None,
|
chroma_server_port=chroma_server_port or None,
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
|
|
@ -104,6 +103,4 @@ class ChromaComponent(CustomComponent):
|
||||||
client_settings=chroma_settings,
|
client_settings=chroma_settings,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Chroma(
|
return Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
|
||||||
persist_directory=persist_directory, client_settings=chroma_settings
|
|
||||||
)
|
|
||||||
|
|
|
||||||
64
src/backend/langflow/components/vectorstores/Redis.py
Normal file
64
src/backend/langflow/components/vectorstores/Redis.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
from langchain.vectorstores.redis import Redis
|
||||||
|
from langchain.schema import Document
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
|
||||||
|
class RedisComponent(CustomComponent):
|
||||||
|
"""
|
||||||
|
A custom component for implementing a Vector Store using Redis.
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name: str = "Redis"
|
||||||
|
description: str = "Implementation of Vector Store using Redis"
|
||||||
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/redis"
|
||||||
|
beta = True
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
"""
|
||||||
|
Builds the configuration for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: A dictionary containing the configuration options for the component.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
||||||
|
"code": {"show": False, "display_name": "Code"},
|
||||||
|
"documents": {"display_name": "Documents", "is_list": True},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"redis_server_url": {
|
||||||
|
"display_name": "Redis Server Connection String",
|
||||||
|
"advanced": False,
|
||||||
|
},
|
||||||
|
"redis_index_name": {"display_name": "Redis Index", "advanced": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
embedding: Embeddings,
|
||||||
|
redis_server_url: str,
|
||||||
|
redis_index_name: str,
|
||||||
|
documents: Optional[Document] = None,
|
||||||
|
) -> VectorStore:
|
||||||
|
"""
|
||||||
|
Builds the Vector Store or BaseRetriever object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- embedding (Embeddings): The embeddings to use for the Vector Store.
|
||||||
|
- documents (Optional[Document]): The documents to use for the Vector Store.
|
||||||
|
- redis_index_name (str): The name of the Redis index.
|
||||||
|
- redis_server_url (str): The URL for the Redis server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- VectorStore: The Vector Store object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Redis.from_documents(
|
||||||
|
documents=documents, # type: ignore
|
||||||
|
embedding=embedding,
|
||||||
|
redis_url=redis_server_url,
|
||||||
|
index_name=redis_index_name,
|
||||||
|
)
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
from langflow import CustomComponent
|
|
||||||
|
|
||||||
|
from langchain.schema import BaseRetriever, Document
|
||||||
from langchain.vectorstores import Vectara
|
from langchain.vectorstores import Vectara
|
||||||
from langchain.schema import Document
|
|
||||||
from langchain.vectorstores.base import VectorStore
|
from langchain.vectorstores.base import VectorStore
|
||||||
from langchain.schema import BaseRetriever
|
|
||||||
from langchain.embeddings.base import Embeddings
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
|
||||||
class VectaraComponent(CustomComponent):
|
class VectaraComponent(CustomComponent):
|
||||||
display_name: str = "Vectara"
|
display_name: str = "Vectara"
|
||||||
description: str = "Implementation of Vector Store using Vectara"
|
description: str = "Implementation of Vector Store using Vectara"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
# api key should be password = True
|
# api key should be password = True
|
||||||
field_config = {
|
field_config = {
|
||||||
|
|
@ -22,7 +19,6 @@ class VectaraComponent(CustomComponent):
|
||||||
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
|
"vectara_api_key": {"display_name": "Vectara API Key", "password": True},
|
||||||
"code": {"show": False},
|
"code": {"show": False},
|
||||||
"documents": {"display_name": "Documents"},
|
"documents": {"display_name": "Documents"},
|
||||||
"embedding": {"display_name": "Embedding"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
|
|
@ -30,21 +26,21 @@ class VectaraComponent(CustomComponent):
|
||||||
vectara_customer_id: str,
|
vectara_customer_id: str,
|
||||||
vectara_corpus_id: str,
|
vectara_corpus_id: str,
|
||||||
vectara_api_key: str,
|
vectara_api_key: str,
|
||||||
embedding: Optional[Embeddings] = None,
|
|
||||||
documents: Optional[Document] = None,
|
documents: Optional[Document] = None,
|
||||||
) -> Union[VectorStore, BaseRetriever]:
|
) -> Union[VectorStore, BaseRetriever]:
|
||||||
# If documents, then we need to create a Vectara instance using .from_documents
|
# If documents, then we need to create a Vectara instance using .from_documents
|
||||||
if documents is not None and embedding is not None:
|
if documents is not None:
|
||||||
return Vectara.from_documents(
|
return Vectara.from_documents(
|
||||||
documents=documents, # type: ignore
|
documents=documents, # type: ignore
|
||||||
vectara_customer_id=vectara_customer_id,
|
vectara_customer_id=vectara_customer_id,
|
||||||
vectara_corpus_id=vectara_corpus_id,
|
vectara_corpus_id=vectara_corpus_id,
|
||||||
vectara_api_key=vectara_api_key,
|
vectara_api_key=vectara_api_key,
|
||||||
embedding=embedding,
|
source="langflow",
|
||||||
)
|
)
|
||||||
|
|
||||||
return Vectara(
|
return Vectara(
|
||||||
vectara_customer_id=vectara_customer_id,
|
vectara_customer_id=vectara_customer_id,
|
||||||
vectara_corpus_id=vectara_corpus_id,
|
vectara_corpus_id=vectara_corpus_id,
|
||||||
vectara_api_key=vectara_api_key,
|
vectara_api_key=vectara_api_key,
|
||||||
|
source="langflow",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
74
src/backend/langflow/components/vectorstores/pgvector.py
Normal file
74
src/backend/langflow/components/vectorstores/pgvector.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
from typing import Optional, List
|
||||||
|
from langflow import CustomComponent
|
||||||
|
|
||||||
|
from langchain.vectorstores.pgvector import PGVector
|
||||||
|
from langchain.schema import Document
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
|
from langchain.embeddings.base import Embeddings
|
||||||
|
|
||||||
|
|
||||||
|
class PostgresqlVectorComponent(CustomComponent):
|
||||||
|
"""
|
||||||
|
A custom component for implementing a Vector Store using PostgreSQL.
|
||||||
|
"""
|
||||||
|
|
||||||
|
display_name: str = "PGVector"
|
||||||
|
description: str = "Implementation of Vector Store using PostgreSQL"
|
||||||
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||||
|
beta = True
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
"""
|
||||||
|
Builds the configuration for the component.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- dict: A dictionary containing the configuration options for the component.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
||||||
|
"code": {"show": True, "display_name": "Code"},
|
||||||
|
"documents": {"display_name": "Documents", "is_list": True},
|
||||||
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
"pg_server_url": {
|
||||||
|
"display_name": "PostgreSQL Server Connection String",
|
||||||
|
"advanced": False,
|
||||||
|
},
|
||||||
|
"collection_name": {"display_name": "Table", "advanced": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
embedding: Embeddings,
|
||||||
|
pg_server_url: str,
|
||||||
|
collection_name: str,
|
||||||
|
documents: Optional[List[Document]] = None,
|
||||||
|
) -> VectorStore:
|
||||||
|
"""
|
||||||
|
Builds the Vector Store or BaseRetriever object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- embedding (Embeddings): The embeddings to use for the Vector Store.
|
||||||
|
- documents (Optional[Document]): The documents to use for the Vector Store.
|
||||||
|
- collection_name (str): The name of the PG table.
|
||||||
|
- pg_server_url (str): The URL for the PG server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- VectorStore: The Vector Store object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if documents is None:
|
||||||
|
return PGVector.from_existing_index(
|
||||||
|
embedding=embedding,
|
||||||
|
collection_name=collection_name,
|
||||||
|
connection_string=pg_server_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
return PGVector.from_documents(
|
||||||
|
embedding=embedding,
|
||||||
|
documents=documents,
|
||||||
|
collection_name=collection_name,
|
||||||
|
connection_string=pg_server_url,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to build PGVector: {e}")
|
||||||
|
|
@ -14,14 +14,14 @@ agents:
|
||||||
SQLAgent:
|
SQLAgent:
|
||||||
documentation: ""
|
documentation: ""
|
||||||
chains:
|
chains:
|
||||||
LLMChain:
|
# LLMChain:
|
||||||
documentation: "https://python.langchain.com/docs/modules/chains/foundational/llm_chain"
|
# documentation: "https://python.langchain.com/docs/modules/chains/foundational/llm_chain"
|
||||||
LLMMathChain:
|
LLMMathChain:
|
||||||
documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_math"
|
documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_math"
|
||||||
LLMCheckerChain:
|
LLMCheckerChain:
|
||||||
documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
|
documentation: "https://python.langchain.com/docs/modules/chains/additional/llm_checker"
|
||||||
ConversationChain:
|
# ConversationChain:
|
||||||
documentation: ""
|
# documentation: ""
|
||||||
SeriesCharacterChain:
|
SeriesCharacterChain:
|
||||||
documentation: ""
|
documentation: ""
|
||||||
MidJourneyPromptChain:
|
MidJourneyPromptChain:
|
||||||
|
|
@ -106,6 +106,9 @@ embeddings:
|
||||||
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:
|
VertexAIEmbeddings:
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/google_vertex_ai_palm"
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/google_vertex_ai_palm"
|
||||||
|
AmazonBedrockEmbeddings:
|
||||||
|
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/bedrock"
|
||||||
|
|
||||||
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"
|
||||||
|
|
@ -265,8 +268,8 @@ retrievers:
|
||||||
# ZepRetriever:
|
# ZepRetriever:
|
||||||
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
|
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
|
||||||
vectorstores:
|
vectorstores:
|
||||||
Chroma:
|
# Chroma:
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
|
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
|
||||||
Qdrant:
|
Qdrant:
|
||||||
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
|
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
|
||||||
Weaviate:
|
Weaviate:
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,28 @@
|
||||||
# LANGCHAIN_BASE_TYPES = {
|
|
||||||
# "Chain": Chain,
|
|
||||||
# "AgentExecutor": AgentExecutor,
|
|
||||||
# "Tool": Tool,
|
|
||||||
# "BaseLLM": BaseLLM,
|
|
||||||
# "PromptTemplate": PromptTemplate,
|
|
||||||
# "BaseLoader": BaseLoader,
|
|
||||||
# "Document": Document,
|
|
||||||
# "TextSplitter": TextSplitter,
|
|
||||||
# "VectorStore": VectorStore,
|
|
||||||
# "Embeddings": Embeddings,
|
|
||||||
# "BaseRetriever": BaseRetriever,
|
|
||||||
# "BaseOutputParser": BaseOutputParser,
|
|
||||||
# "BaseMemory": BaseMemory,
|
|
||||||
# "BaseChatMemory": BaseChatMemory,
|
|
||||||
# }
|
|
||||||
from .constants import (
|
from .constants import (
|
||||||
Tool,
|
AgentExecutor,
|
||||||
PromptTemplate,
|
|
||||||
Chain,
|
|
||||||
BaseChatMemory,
|
BaseChatMemory,
|
||||||
|
BaseLanguageModel,
|
||||||
BaseLLM,
|
BaseLLM,
|
||||||
BaseLoader,
|
BaseLoader,
|
||||||
BaseMemory,
|
BaseMemory,
|
||||||
BaseOutputParser,
|
BaseOutputParser,
|
||||||
|
BasePromptTemplate,
|
||||||
BaseRetriever,
|
BaseRetriever,
|
||||||
VectorStore,
|
Callable,
|
||||||
Embeddings,
|
Chain,
|
||||||
TextSplitter,
|
ChatPromptTemplate,
|
||||||
Document,
|
|
||||||
AgentExecutor,
|
|
||||||
NestedDict,
|
|
||||||
Data,
|
Data,
|
||||||
|
Document,
|
||||||
|
Embeddings,
|
||||||
|
NestedDict,
|
||||||
|
Object,
|
||||||
|
PromptTemplate,
|
||||||
|
TextSplitter,
|
||||||
|
Tool,
|
||||||
|
VectorStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"NestedDict",
|
"NestedDict",
|
||||||
"Data",
|
"Data",
|
||||||
|
|
@ -41,6 +31,7 @@ __all__ = [
|
||||||
"Chain",
|
"Chain",
|
||||||
"BaseChatMemory",
|
"BaseChatMemory",
|
||||||
"BaseLLM",
|
"BaseLLM",
|
||||||
|
"BaseLanguageModel",
|
||||||
"BaseLoader",
|
"BaseLoader",
|
||||||
"BaseMemory",
|
"BaseMemory",
|
||||||
"BaseOutputParser",
|
"BaseOutputParser",
|
||||||
|
|
@ -50,4 +41,8 @@ __all__ = [
|
||||||
"TextSplitter",
|
"TextSplitter",
|
||||||
"Document",
|
"Document",
|
||||||
"AgentExecutor",
|
"AgentExecutor",
|
||||||
|
"Object",
|
||||||
|
"Callable",
|
||||||
|
"BasePromptTemplate",
|
||||||
|
"ChatPromptTemplate",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
|
from typing import Callable, Dict, Union
|
||||||
|
|
||||||
from langchain.agents.agent import AgentExecutor
|
from langchain.agents.agent import AgentExecutor
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.document_loaders.base import BaseLoader
|
from langchain.document_loaders.base import BaseLoader
|
||||||
from langchain.llms.base import BaseLLM
|
from langchain.llms.base import BaseLanguageModel, BaseLLM
|
||||||
from langchain.memory.chat_memory import BaseChatMemory
|
from langchain.memory.chat_memory import BaseChatMemory
|
||||||
from langchain.prompts import PromptTemplate
|
from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||||
from langchain.schema import BaseOutputParser, BaseRetriever, Document
|
from langchain.schema import BaseOutputParser, BaseRetriever, Document
|
||||||
from langchain.schema.embeddings import Embeddings
|
from langchain.schema.embeddings import Embeddings
|
||||||
from langchain.schema.memory import BaseMemory
|
from langchain.schema.memory import BaseMemory
|
||||||
from langchain.text_splitter import TextSplitter
|
from langchain.text_splitter import TextSplitter
|
||||||
from langchain.tools import Tool
|
from langchain.tools import Tool
|
||||||
from langchain.vectorstores.base import VectorStore
|
from langchain.vectorstores.base import VectorStore
|
||||||
from typing import Union, Dict
|
|
||||||
|
|
||||||
# Type alias for more complex dicts
|
# Type alias for more complex dicts
|
||||||
NestedDict = Dict[str, Union[str, Dict]]
|
NestedDict = Dict[str, Union[str, Dict]]
|
||||||
|
|
||||||
|
|
||||||
|
class Object:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -25,7 +30,10 @@ LANGCHAIN_BASE_TYPES = {
|
||||||
"AgentExecutor": AgentExecutor,
|
"AgentExecutor": AgentExecutor,
|
||||||
"Tool": Tool,
|
"Tool": Tool,
|
||||||
"BaseLLM": BaseLLM,
|
"BaseLLM": BaseLLM,
|
||||||
|
"BaseLanguageModel": BaseLanguageModel,
|
||||||
"PromptTemplate": PromptTemplate,
|
"PromptTemplate": PromptTemplate,
|
||||||
|
"ChatPromptTemplate": ChatPromptTemplate,
|
||||||
|
"BasePromptTemplate": BasePromptTemplate,
|
||||||
"BaseLoader": BaseLoader,
|
"BaseLoader": BaseLoader,
|
||||||
"Document": Document,
|
"Document": Document,
|
||||||
"TextSplitter": TextSplitter,
|
"TextSplitter": TextSplitter,
|
||||||
|
|
@ -47,4 +55,6 @@ CUSTOM_COMPONENT_SUPPORTED_TYPES = {
|
||||||
"dict": dict,
|
"dict": dict,
|
||||||
"NestedDict": NestedDict,
|
"NestedDict": NestedDict,
|
||||||
"Data": Data,
|
"Data": Data,
|
||||||
|
"Object": Object,
|
||||||
|
"Callable": Callable,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class SourceHandle(BaseModel):
|
class SourceHandle(BaseModel):
|
||||||
baseClasses: List[str] = Field(
|
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
|
||||||
..., description="List of base classes for the source handle."
|
|
||||||
)
|
|
||||||
dataType: str = Field(..., description="Data type for the source handle.")
|
dataType: str = Field(..., description="Data type for the source handle.")
|
||||||
id: str = Field(..., description="Unique identifier for the source handle.")
|
id: str = Field(..., description="Unique identifier for the source handle.")
|
||||||
|
|
||||||
|
|
@ -18,9 +16,7 @@ class SourceHandle(BaseModel):
|
||||||
class TargetHandle(BaseModel):
|
class TargetHandle(BaseModel):
|
||||||
fieldName: str = Field(..., description="Field name for the target handle.")
|
fieldName: str = Field(..., description="Field name for the target handle.")
|
||||||
id: str = Field(..., description="Unique identifier for the target handle.")
|
id: str = Field(..., description="Unique identifier for the target handle.")
|
||||||
inputTypes: Optional[List[str]] = Field(
|
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
|
||||||
None, description="List of input types for the target handle."
|
|
||||||
)
|
|
||||||
type: str = Field(..., description="Type of the target handle.")
|
type: str = Field(..., description="Type of the target handle.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -49,23 +45,17 @@ class Edge:
|
||||||
|
|
||||||
def validate_handles(self) -> None:
|
def validate_handles(self) -> None:
|
||||||
if self.target_handle.inputTypes is None:
|
if self.target_handle.inputTypes is None:
|
||||||
self.valid_handles = (
|
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
|
||||||
self.target_handle.type in self.source_handle.baseClasses
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.valid_handles = (
|
self.valid_handles = (
|
||||||
any(
|
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
|
||||||
baseClass in self.target_handle.inputTypes
|
|
||||||
for baseClass in self.source_handle.baseClasses
|
|
||||||
)
|
|
||||||
or self.target_handle.type in self.source_handle.baseClasses
|
or self.target_handle.type in self.source_handle.baseClasses
|
||||||
)
|
)
|
||||||
if not self.valid_handles:
|
if not self.valid_handles:
|
||||||
logger.debug(self.source_handle)
|
logger.debug(self.source_handle)
|
||||||
logger.debug(self.target_handle)
|
logger.debug(self.target_handle)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
|
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has invalid handles"
|
||||||
f"has invalid handles"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
|
|
@ -87,11 +77,7 @@ class Edge:
|
||||||
# Both lists contain strings and sometimes a string contains the value we are
|
# Both lists contain strings and sometimes a string contains the value we are
|
||||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||||
# so we need to check if any of the strings in source_types is in target_reqs
|
# so we need to check if any of the strings in source_types is in target_reqs
|
||||||
self.valid = any(
|
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
|
||||||
output in target_req
|
|
||||||
for output in self.source_types
|
|
||||||
for target_req in self.target_reqs
|
|
||||||
)
|
|
||||||
# Get what type of input the target node is expecting
|
# Get what type of input the target node is expecting
|
||||||
|
|
||||||
self.matched_type = next(
|
self.matched_type = next(
|
||||||
|
|
@ -103,8 +89,7 @@ class Edge:
|
||||||
logger.debug(self.source_types)
|
logger.debug(self.source_types)
|
||||||
logger.debug(self.target_reqs)
|
logger.debug(self.target_reqs)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
|
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has no matched type"
|
||||||
f"has no matched type"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
|
|
@ -117,8 +102,4 @@ class Edge:
|
||||||
return hash(self.__repr__())
|
return hash(self.__repr__())
|
||||||
|
|
||||||
def __eq__(self, __value: object) -> bool:
|
def __eq__(self, __value: object) -> bool:
|
||||||
return (
|
return self.__repr__() == __value.__repr__() if isinstance(__value, Edge) else False
|
||||||
self.__repr__() == __value.__repr__()
|
|
||||||
if isinstance(__value, Edge)
|
|
||||||
else False
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
from typing import Dict, Generator, List, Type, Union
|
from typing import Dict, Generator, List, Type, Union
|
||||||
|
|
||||||
|
from langchain.chains.base import Chain
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from langflow.graph.edge.base import Edge
|
from langflow.graph.edge.base import Edge
|
||||||
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
||||||
from langflow.graph.graph.utils import process_flow
|
from langflow.graph.graph.utils import process_flow
|
||||||
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, LLMVertex, ToolkitVertex
|
||||||
FileToolVertex,
|
|
||||||
LLMVertex,
|
|
||||||
ToolkitVertex,
|
|
||||||
)
|
|
||||||
from langflow.interface.tools.constants import FILE_TOOLS
|
from langflow.interface.tools.constants import FILE_TOOLS
|
||||||
from langflow.utils import payload
|
from langflow.utils import payload
|
||||||
from loguru import logger
|
|
||||||
from langchain.chains.base import Chain
|
|
||||||
|
|
||||||
|
|
||||||
class Graph:
|
class Graph:
|
||||||
|
|
@ -31,8 +28,8 @@ class Graph:
|
||||||
for node in self._nodes:
|
for node in self._nodes:
|
||||||
if node_id := node.get("id"):
|
if node_id := node.get("id"):
|
||||||
self.top_level_nodes.append(node_id)
|
self.top_level_nodes.append(node_id)
|
||||||
|
|
||||||
self._graph_data = process_flow(self.raw_graph_data)
|
self._graph_data = process_flow(self.raw_graph_data)
|
||||||
|
|
||||||
self._nodes = self._graph_data["nodes"]
|
self._nodes = self._graph_data["nodes"]
|
||||||
self._edges = self._graph_data["edges"]
|
self._edges = self._graph_data["edges"]
|
||||||
self._build_graph()
|
self._build_graph()
|
||||||
|
|
@ -104,9 +101,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
if not self._validate_node(node):
|
if not self._validate_node(node):
|
||||||
raise ValueError(
|
raise ValueError(f"{node.vertex_type} is not connected to any other components")
|
||||||
f"{node.vertex_type} is not connected to any other components"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_node(self, node: Vertex) -> bool:
|
def _validate_node(self, node: Vertex) -> bool:
|
||||||
"""Validates a node."""
|
"""Validates a node."""
|
||||||
|
|
@ -119,18 +114,16 @@ class Graph:
|
||||||
|
|
||||||
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
|
def get_nodes_with_target(self, node: Vertex) -> List[Vertex]:
|
||||||
"""Returns the nodes connected to a node."""
|
"""Returns the nodes connected to a node."""
|
||||||
connected_nodes: List[Vertex] = [
|
connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node]
|
||||||
edge.source for edge in self.edges if edge.target == node
|
|
||||||
]
|
|
||||||
return connected_nodes
|
return connected_nodes
|
||||||
|
|
||||||
def build(self) -> Chain:
|
async def build(self) -> Chain:
|
||||||
"""Builds the graph."""
|
"""Builds the graph."""
|
||||||
# Get root node
|
# Get root node
|
||||||
root_node = payload.get_root_node(self)
|
root_node = payload.get_root_node(self)
|
||||||
if root_node is None:
|
if root_node is None:
|
||||||
raise ValueError("No root node found")
|
raise ValueError("No root node found")
|
||||||
return root_node.build()
|
return await root_node.build()
|
||||||
|
|
||||||
def topological_sort(self) -> List[Vertex]:
|
def topological_sort(self) -> List[Vertex]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -149,9 +142,7 @@ class Graph:
|
||||||
def dfs(node):
|
def dfs(node):
|
||||||
if state[node] == 1:
|
if state[node] == 1:
|
||||||
# We have a cycle
|
# We have a cycle
|
||||||
raise ValueError(
|
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||||
"Graph contains a cycle, cannot perform topological sort"
|
|
||||||
)
|
|
||||||
if state[node] == 0:
|
if state[node] == 0:
|
||||||
state[node] = 1
|
state[node] = 1
|
||||||
for edge in node.edges:
|
for edge in node.edges:
|
||||||
|
|
@ -245,7 +236,5 @@ class Graph:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
node_ids = [node.id for node in self.nodes]
|
node_ids = [node.id for node in self.nodes]
|
||||||
edges_repr = "\n".join(
|
edges_repr = "\n".join([f"{edge.source.id} --> {edge.target.id}" for edge in self.edges])
|
||||||
[f"{edge.source.id} --> {edge.target.id}" for edge in self.edges]
|
|
||||||
)
|
|
||||||
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"
|
return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,7 @@ class VertexTypesDict(LazyLoadDictBase):
|
||||||
**{t: types.DocumentLoaderVertex for t in documentloader_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.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||||
**{t: types.OutputParserVertex for t in output_parser_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.CustomComponentVertex
|
|
||||||
for t in custom_component_creator.to_list()
|
|
||||||
},
|
|
||||||
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from collections import deque
|
|
||||||
import copy
|
import copy
|
||||||
|
from collections import deque
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
def find_last_node(nodes, edges):
|
def find_last_node(nodes, edges):
|
||||||
|
|
@ -28,23 +29,14 @@ def ungroup_node(group_node_data, base_flow):
|
||||||
g_edges = flow["data"]["edges"]
|
g_edges = flow["data"]["edges"]
|
||||||
|
|
||||||
# Redirect edges to the correct proxy node
|
# Redirect edges to the correct proxy node
|
||||||
updated_edges = get_updated_edges(
|
updated_edges = get_updated_edges(base_flow, g_nodes, g_edges, group_node_data["id"])
|
||||||
base_flow, g_nodes, g_edges, group_node_data["id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update template values
|
# Update template values
|
||||||
update_template(template, g_nodes)
|
update_template(template, g_nodes)
|
||||||
|
|
||||||
nodes = [
|
nodes = [n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]] + g_nodes
|
||||||
n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]
|
|
||||||
] + g_nodes
|
|
||||||
edges = (
|
edges = (
|
||||||
[
|
[e for e in base_flow["edges"] if e["target"] != group_node_data["id"] and e["source"] != group_node_data["id"]]
|
||||||
e
|
|
||||||
for e in base_flow["edges"]
|
|
||||||
if e["target"] != group_node_data["id"]
|
|
||||||
and e["source"] != group_node_data["id"]
|
|
||||||
]
|
|
||||||
+ g_edges
|
+ g_edges
|
||||||
+ updated_edges
|
+ updated_edges
|
||||||
)
|
)
|
||||||
|
|
@ -55,6 +47,38 @@ def ungroup_node(group_node_data, base_flow):
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
def raw_topological_sort(nodes, edges) -> List[Dict]:
|
||||||
|
# Redefine the above function but using the nodes and self._edges
|
||||||
|
# which are dicts instead of Vertex and Edge objects
|
||||||
|
# nodes have an id, edges have a source and target keys
|
||||||
|
# return a list of node ids in topological order
|
||||||
|
|
||||||
|
# States: 0 = unvisited, 1 = visiting, 2 = visited
|
||||||
|
state = {node["id"]: 0 for node in nodes}
|
||||||
|
nodes_dict = {node["id"]: node for node in nodes}
|
||||||
|
sorted_vertices = []
|
||||||
|
|
||||||
|
def dfs(node):
|
||||||
|
if state[node] == 1:
|
||||||
|
# We have a cycle
|
||||||
|
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||||
|
if state[node] == 0:
|
||||||
|
state[node] = 1
|
||||||
|
for edge in edges:
|
||||||
|
if edge["source"] == node:
|
||||||
|
dfs(edge["target"])
|
||||||
|
state[node] = 2
|
||||||
|
sorted_vertices.append(node)
|
||||||
|
|
||||||
|
# Visit each node
|
||||||
|
for node in nodes:
|
||||||
|
if state[node["id"]] == 0:
|
||||||
|
dfs(node["id"])
|
||||||
|
|
||||||
|
reverse_sorted = list(reversed(sorted_vertices))
|
||||||
|
return [nodes_dict[node_id] for node_id in reverse_sorted]
|
||||||
|
|
||||||
|
|
||||||
def process_flow(flow_object):
|
def process_flow(flow_object):
|
||||||
cloned_flow = copy.deepcopy(flow_object)
|
cloned_flow = copy.deepcopy(flow_object)
|
||||||
processed_nodes = set() # To keep track of processed nodes
|
processed_nodes = set() # To keep track of processed nodes
|
||||||
|
|
@ -66,11 +90,7 @@ def process_flow(flow_object):
|
||||||
if node_id in processed_nodes:
|
if node_id in processed_nodes:
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if node.get("data") and node["data"].get("node") and node["data"]["node"].get("flow"):
|
||||||
node.get("data")
|
|
||||||
and node["data"].get("node")
|
|
||||||
and node["data"]["node"].get("flow")
|
|
||||||
):
|
|
||||||
process_flow(node["data"]["node"]["flow"]["data"])
|
process_flow(node["data"]["node"]["flow"]["data"])
|
||||||
new_nodes = ungroup_node(node["data"], cloned_flow)
|
new_nodes = ungroup_node(node["data"], cloned_flow)
|
||||||
# Add new nodes to the queue for future processing
|
# Add new nodes to the queue for future processing
|
||||||
|
|
@ -79,7 +99,8 @@ def process_flow(flow_object):
|
||||||
# Mark node as processed
|
# Mark node as processed
|
||||||
processed_nodes.add(node_id)
|
processed_nodes.add(node_id)
|
||||||
|
|
||||||
nodes_to_process = deque(cloned_flow["nodes"])
|
sorted_nodes_list = raw_topological_sort(cloned_flow["nodes"], cloned_flow["edges"])
|
||||||
|
nodes_to_process = deque(sorted_nodes_list)
|
||||||
|
|
||||||
while nodes_to_process:
|
while nodes_to_process:
|
||||||
node = nodes_to_process.popleft()
|
node = nodes_to_process.popleft()
|
||||||
|
|
@ -108,29 +129,23 @@ def update_template(template, g_nodes):
|
||||||
if node_index != -1:
|
if node_index != -1:
|
||||||
display_name = None
|
display_name = None
|
||||||
show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
|
show = g_nodes[node_index]["data"]["node"]["template"][field]["show"]
|
||||||
advanced = g_nodes[node_index]["data"]["node"]["template"][field][
|
advanced = g_nodes[node_index]["data"]["node"]["template"][field]["advanced"]
|
||||||
"advanced"
|
|
||||||
]
|
|
||||||
if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
|
if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]:
|
||||||
display_name = g_nodes[node_index]["data"]["node"]["template"][field][
|
display_name = g_nodes[node_index]["data"]["node"]["template"][field]["display_name"]
|
||||||
"display_name"
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
display_name = g_nodes[node_index]["data"]["node"]["template"][field][
|
display_name = g_nodes[node_index]["data"]["node"]["template"][field]["name"]
|
||||||
"name"
|
|
||||||
]
|
|
||||||
|
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field] = value
|
g_nodes[node_index]["data"]["node"]["template"][field] = value
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
|
g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field][
|
g_nodes[node_index]["data"]["node"]["template"][field]["advanced"] = advanced
|
||||||
"advanced"
|
g_nodes[node_index]["data"]["node"]["template"][field]["display_name"] = display_name
|
||||||
] = advanced
|
|
||||||
g_nodes[node_index]["data"]["node"]["template"][field][
|
|
||||||
"display_name"
|
|
||||||
] = display_name
|
|
||||||
|
|
||||||
|
|
||||||
def update_target_handle(new_edge, g_nodes, group_node_id):
|
def update_target_handle(
|
||||||
|
new_edge,
|
||||||
|
g_nodes,
|
||||||
|
group_node_id,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Updates the target handle of a given edge if it is a proxy node.
|
Updates the target handle of a given edge if it is a proxy node.
|
||||||
|
|
||||||
|
|
@ -147,6 +162,8 @@ def update_target_handle(new_edge, g_nodes, group_node_id):
|
||||||
proxy_id = target_handle["proxy"]["id"]
|
proxy_id = target_handle["proxy"]["id"]
|
||||||
if node := next((n for n in g_nodes if n["id"] == proxy_id), None):
|
if node := next((n for n in g_nodes if n["id"] == proxy_id), None):
|
||||||
set_new_target_handle(proxy_id, new_edge, target_handle, node)
|
set_new_target_handle(proxy_id, new_edge, target_handle, node)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Group node {group_node_id} has an invalid target proxy node {proxy_id}")
|
||||||
return new_edge
|
return new_edge
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
import ast
|
import ast
|
||||||
|
import inspect
|
||||||
import pickle
|
import pickle
|
||||||
|
import types
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from langflow.graph.utils import UnbuiltObject
|
from langflow.graph.utils import UnbuiltObject
|
||||||
from langflow.graph.vertex.utils import is_basic_type
|
from langflow.graph.vertex.utils import is_basic_type
|
||||||
from langflow.interface.initialize import loading
|
from langflow.interface.initialize import loading
|
||||||
from langflow.interface.listing import lazy_load_dict
|
from langflow.interface.listing import lazy_load_dict
|
||||||
from langflow.utils.constants import DIRECT_TYPES
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
from loguru import logger
|
|
||||||
from langflow.utils.util import sync_to_async
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import types
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from langflow.graph.edge.base import Edge
|
from langflow.graph.edge.base import Edge
|
||||||
|
|
||||||
|
|
@ -51,9 +49,7 @@ class Vertex:
|
||||||
self.params.pop(target_param, None)
|
self.params.pop(target_param, None)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if target_param in self.params and not is_basic_type(
|
if target_param in self.params and not is_basic_type(self.params[target_param]):
|
||||||
self.params[target_param]
|
|
||||||
):
|
|
||||||
# edge.source.params = {}
|
# edge.source.params = {}
|
||||||
edge.source._build_params()
|
edge.source._build_params()
|
||||||
edge.source._built_object = UnbuiltObject()
|
edge.source._built_object = UnbuiltObject()
|
||||||
|
|
@ -99,29 +95,17 @@ class Vertex:
|
||||||
def _parse_data(self) -> None:
|
def _parse_data(self) -> None:
|
||||||
self.data = self._data["data"]
|
self.data = self._data["data"]
|
||||||
self.output = self.data["node"]["base_classes"]
|
self.output = self.data["node"]["base_classes"]
|
||||||
template_dicts = {
|
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.required_inputs = [
|
self.required_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if value["required"]
|
|
||||||
]
|
]
|
||||||
self.optional_inputs = [
|
self.optional_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if not value["required"]
|
|
||||||
]
|
]
|
||||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||||
self.optional_inputs.extend(
|
self.optional_inputs.extend(
|
||||||
[
|
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
||||||
input_type
|
|
||||||
for value in template_dicts.values()
|
|
||||||
for input_type in value.get("input_types", [])
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
|
|
@ -160,11 +144,7 @@ class Vertex:
|
||||||
# and use that as the value for the param
|
# and use that as the value for the param
|
||||||
# If the type is "str", then we need to get the value of the "value" key
|
# If the type is "str", then we need to get the value of the "value" key
|
||||||
# and use that as the value for the param
|
# and use that as the value for the param
|
||||||
template_dict = {
|
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
params = self.params.copy() if self.params else {}
|
params = self.params.copy() if self.params else {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -209,11 +189,7 @@ class Vertex:
|
||||||
# before passing it to the build method
|
# before passing it to the build method
|
||||||
_value = value.get("value")
|
_value = value.get("value")
|
||||||
if isinstance(_value, list):
|
if isinstance(_value, list):
|
||||||
params[key] = {
|
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
||||||
k: v
|
|
||||||
for item in value.get("value", [])
|
|
||||||
for k, v in item.items()
|
|
||||||
}
|
|
||||||
elif isinstance(_value, dict):
|
elif isinstance(_value, dict):
|
||||||
params[key] = _value
|
params[key] = _value
|
||||||
elif value.get("type") == "int" and value.get("value") is not None:
|
elif value.get("type") == "int" and value.get("value") is not None:
|
||||||
|
|
@ -238,18 +214,18 @@ class Vertex:
|
||||||
self._raw_params = params
|
self._raw_params = params
|
||||||
self.params = params
|
self.params = params
|
||||||
|
|
||||||
def _build(self, user_id=None):
|
async def _build(self, user_id=None):
|
||||||
"""
|
"""
|
||||||
Initiate the build process.
|
Initiate the build process.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Building {self.vertex_type}")
|
logger.debug(f"Building {self.vertex_type}")
|
||||||
self._build_each_node_in_params_dict(user_id)
|
await self._build_each_node_in_params_dict(user_id)
|
||||||
self._get_and_instantiate_class(user_id)
|
await self._get_and_instantiate_class(user_id)
|
||||||
self._validate_built_object()
|
self._validate_built_object()
|
||||||
|
|
||||||
self._built = True
|
self._built = True
|
||||||
|
|
||||||
def _build_each_node_in_params_dict(self, user_id=None):
|
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||||
"""
|
"""
|
||||||
Iterates over each node in the params dictionary and builds it.
|
Iterates over each node in the params dictionary and builds it.
|
||||||
"""
|
"""
|
||||||
|
|
@ -258,9 +234,9 @@ class Vertex:
|
||||||
if value == self:
|
if value == self:
|
||||||
del self.params[key]
|
del self.params[key]
|
||||||
continue
|
continue
|
||||||
self._build_node_and_update_params(key, value, user_id)
|
await self._build_node_and_update_params(key, value, user_id)
|
||||||
elif isinstance(value, list) and self._is_list_of_nodes(value):
|
elif isinstance(value, list) and self._is_list_of_nodes(value):
|
||||||
self._build_list_of_nodes_and_update_params(key, value, user_id)
|
await self._build_list_of_nodes_and_update_params(key, value, user_id)
|
||||||
|
|
||||||
def _is_node(self, value):
|
def _is_node(self, value):
|
||||||
"""
|
"""
|
||||||
|
|
@ -274,7 +250,7 @@ class Vertex:
|
||||||
"""
|
"""
|
||||||
return all(self._is_node(node) for node in value)
|
return all(self._is_node(node) for node in value)
|
||||||
|
|
||||||
def get_result(self, user_id=None, timeout=None) -> Any:
|
async def get_result(self, user_id=None, timeout=None) -> Any:
|
||||||
# Check if the Vertex was built already
|
# Check if the Vertex was built already
|
||||||
if self._built:
|
if self._built:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
@ -290,29 +266,27 @@ class Vertex:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# If there's no task_id, build the vertex locally
|
# If there's no task_id, build the vertex locally
|
||||||
self.build(user_id)
|
await self.build(user_id)
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
def _build_node_and_update_params(self, key, node, user_id=None):
|
async def _build_node_and_update_params(self, key, node, user_id=None):
|
||||||
"""
|
"""
|
||||||
Builds a given node and updates the params dictionary accordingly.
|
Builds a given node and updates the params dictionary accordingly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = node.get_result(user_id)
|
result = await node.get_result(user_id)
|
||||||
self._handle_func(key, result)
|
self._handle_func(key, result)
|
||||||
if isinstance(result, list):
|
if isinstance(result, list):
|
||||||
self._extend_params_list_with_result(key, result)
|
self._extend_params_list_with_result(key, result)
|
||||||
self.params[key] = result
|
self.params[key] = result
|
||||||
|
|
||||||
def _build_list_of_nodes_and_update_params(
|
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||||
self, key, nodes: List["Vertex"], user_id=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
self.params[key] = []
|
self.params[key] = []
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
built = node.get_result(user_id)
|
built = await node.get_result(user_id)
|
||||||
if isinstance(built, list):
|
if isinstance(built, list):
|
||||||
if key not in self.params:
|
if key not in self.params:
|
||||||
self.params[key] = []
|
self.params[key] = []
|
||||||
|
|
@ -342,14 +316,14 @@ class Vertex:
|
||||||
if isinstance(self.params[key], list):
|
if isinstance(self.params[key], list):
|
||||||
self.params[key].extend(result)
|
self.params[key].extend(result)
|
||||||
|
|
||||||
def _get_and_instantiate_class(self, user_id=None):
|
async def _get_and_instantiate_class(self, user_id=None):
|
||||||
"""
|
"""
|
||||||
Gets the class from a dictionary and instantiates it with the params.
|
Gets the class from a dictionary and instantiates it with the params.
|
||||||
"""
|
"""
|
||||||
if self.base_type is None:
|
if self.base_type is None:
|
||||||
raise ValueError(f"Base type for node {self.vertex_type} not found")
|
raise ValueError(f"Base type for node {self.vertex_type} not found")
|
||||||
try:
|
try:
|
||||||
result = loading.instantiate_class(
|
result = await loading.instantiate_class(
|
||||||
node_type=self.vertex_type,
|
node_type=self.vertex_type,
|
||||||
base_type=self.base_type,
|
base_type=self.base_type,
|
||||||
params=self.params,
|
params=self.params,
|
||||||
|
|
@ -358,9 +332,7 @@ class Vertex:
|
||||||
self._update_built_object_and_artifacts(result)
|
self._update_built_object_and_artifacts(result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise ValueError(
|
raise ValueError(f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}") from exc
|
||||||
f"Error building node {self.vertex_type}: {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -384,9 +356,9 @@ class Vertex:
|
||||||
|
|
||||||
raise ValueError(message)
|
raise ValueError(message)
|
||||||
|
|
||||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
self._build(user_id, *args, **kwargs)
|
await self._build(user_id, *args, **kwargs)
|
||||||
|
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
|
|
@ -408,8 +380,4 @@ 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 (
|
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵💫"
|
||||||
"Built sucessfully ✨"
|
|
||||||
if self._built_object is not None
|
|
||||||
else "Failed to build 😵💫"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import ast
|
import ast
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from langflow.graph.vertex.base import Vertex
|
|
||||||
from langflow.graph.utils import flatten_list
|
from langflow.graph.utils import flatten_list
|
||||||
|
from langflow.graph.vertex.base import Vertex
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -34,18 +34,18 @@ class AgentVertex(Vertex):
|
||||||
elif isinstance(source_node, ChainVertex):
|
elif isinstance(source_node, ChainVertex):
|
||||||
self.chains.append(source_node)
|
self.chains.append(source_node)
|
||||||
|
|
||||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
self._set_tools_and_chains()
|
self._set_tools_and_chains()
|
||||||
# First, build the tools
|
# First, build the tools
|
||||||
for tool_node in self.tools:
|
for tool_node in self.tools:
|
||||||
tool_node.build(user_id=user_id)
|
await tool_node.build(user_id=user_id)
|
||||||
|
|
||||||
# Next, build the chains and the rest
|
# Next, build the chains and the rest
|
||||||
for chain_node in self.chains:
|
for chain_node in self.chains:
|
||||||
chain_node.build(tools=self.tools, user_id=user_id)
|
await chain_node.build(tools=self.tools, user_id=user_id)
|
||||||
|
|
||||||
self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
|
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
|
|
@ -62,13 +62,13 @@ class LLMVertex(Vertex):
|
||||||
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
def __init__(self, data: Dict, params: Optional[Dict] = None):
|
||||||
super().__init__(data, base_type="llms", params=params)
|
super().__init__(data, base_type="llms", params=params)
|
||||||
|
|
||||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||||
# LLM is different because some models might take up too much memory
|
# LLM is different because some models might take up too much memory
|
||||||
# or time to load. So we only load them when we need them.ß
|
# or time to load. So we only load them when we need them.ß
|
||||||
if self.vertex_type == self.built_node_type:
|
if self.vertex_type == self.built_node_type:
|
||||||
return self.class_built_object
|
return self.class_built_object
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
self.built_node_type = self.vertex_type
|
self.built_node_type = self.vertex_type
|
||||||
self.class_built_object = self._built_object
|
self.class_built_object = self._built_object
|
||||||
# Avoid deepcopying the LLM
|
# Avoid deepcopying the LLM
|
||||||
|
|
@ -90,11 +90,11 @@ class WrapperVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="wrappers")
|
super().__init__(data, base_type="wrappers")
|
||||||
|
|
||||||
def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
async def build(self, force: bool = False, user_id=None, *args, **kwargs) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
if "headers" in self.params:
|
if "headers" in self.params:
|
||||||
self.params["headers"] = ast.literal_eval(self.params["headers"])
|
self.params["headers"] = ast.literal_eval(self.params["headers"])
|
||||||
self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -107,11 +107,9 @@ class DocumentLoaderVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if self._built_object:
|
if self._built_object:
|
||||||
avg_length = sum(
|
avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len(
|
||||||
len(doc.page_content)
|
self._built_object
|
||||||
for doc in self._built_object
|
)
|
||||||
if hasattr(doc, "page_content")
|
|
||||||
) / len(self._built_object)
|
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
\nAvg. Document Length (characters): {int(avg_length)}
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
Documents: {self._built_object[:3]}..."""
|
Documents: {self._built_object[:3]}..."""
|
||||||
|
|
@ -184,9 +182,7 @@ class TextSplitterVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if self._built_object:
|
if self._built_object:
|
||||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
|
||||||
self._built_object
|
|
||||||
)
|
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
\nAvg. Document Length (characters): {int(avg_length)}
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
\nDocuments: {self._built_object[:3]}..."""
|
\nDocuments: {self._built_object[:3]}..."""
|
||||||
|
|
@ -197,7 +193,7 @@ class ChainVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="chains")
|
super().__init__(data, base_type="chains")
|
||||||
|
|
||||||
def build(
|
async def build(
|
||||||
self,
|
self,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
user_id=None,
|
user_id=None,
|
||||||
|
|
@ -205,14 +201,20 @@ class ChainVertex(Vertex):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
|
# Temporarily remove the code from the params
|
||||||
|
self.params.pop("code", None)
|
||||||
# Check if the chain requires a PromptVertex
|
# Check if the chain requires a PromptVertex
|
||||||
|
|
||||||
|
# Temporarily remove "code" from the params
|
||||||
|
self.params.pop("code", None)
|
||||||
|
|
||||||
for key, value in self.params.items():
|
for key, value in self.params.items():
|
||||||
if isinstance(value, PromptVertex):
|
if isinstance(value, PromptVertex):
|
||||||
# Build the PromptVertex, passing the tools if available
|
# Build the PromptVertex, passing the tools if available
|
||||||
tools = kwargs.get("tools", None)
|
tools = kwargs.get("tools", None)
|
||||||
self.params[key] = value.build(tools=tools, force=force)
|
self.params[key] = await value.build(tools=tools, force=force)
|
||||||
|
|
||||||
self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
|
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
|
|
@ -221,7 +223,7 @@ class PromptVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="prompts")
|
super().__init__(data, base_type="prompts")
|
||||||
|
|
||||||
def build(
|
async def build(
|
||||||
self,
|
self,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
user_id=None,
|
user_id=None,
|
||||||
|
|
@ -230,27 +232,18 @@ class PromptVertex(Vertex):
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
if (
|
if "input_variables" not in self.params or self.params["input_variables"] is None:
|
||||||
"input_variables" not in self.params
|
|
||||||
or self.params["input_variables"] is None
|
|
||||||
):
|
|
||||||
self.params["input_variables"] = []
|
self.params["input_variables"] = []
|
||||||
# Check if it is a ZeroShotPrompt and needs a tool
|
# Check if it is a ZeroShotPrompt and needs a tool
|
||||||
if "ShotPrompt" in self.vertex_type:
|
if "ShotPrompt" in self.vertex_type:
|
||||||
tools = (
|
tools = [await tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
|
||||||
[tool_node.build(user_id=user_id) for tool_node in tools]
|
|
||||||
if tools is not None
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
# flatten the list of tools if it is a list of lists
|
# flatten the list of tools if it is a list of lists
|
||||||
# first check if it is a list
|
# first check if it is a list
|
||||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||||
tools = flatten_list(tools)
|
tools = flatten_list(tools)
|
||||||
self.params["tools"] = tools
|
self.params["tools"] = tools
|
||||||
prompt_params = [
|
prompt_params = [
|
||||||
key
|
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
|
||||||
for key, value in self.params.items()
|
|
||||||
if isinstance(value, str) and key != "format_instructions"
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
prompt_params = ["template"]
|
prompt_params = ["template"]
|
||||||
|
|
@ -260,21 +253,15 @@ class PromptVertex(Vertex):
|
||||||
prompt_text = self.params[param]
|
prompt_text = self.params[param]
|
||||||
variables = extract_input_variables_from_prompt(prompt_text)
|
variables = extract_input_variables_from_prompt(prompt_text)
|
||||||
self.params["input_variables"].extend(variables)
|
self.params["input_variables"].extend(variables)
|
||||||
self.params["input_variables"] = list(
|
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
||||||
set(self.params["input_variables"])
|
|
||||||
)
|
|
||||||
elif isinstance(self.params, dict):
|
elif isinstance(self.params, dict):
|
||||||
self.params.pop("input_variables", None)
|
self.params.pop("input_variables", None)
|
||||||
|
|
||||||
self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
if (
|
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
|
||||||
not self.artifacts
|
|
||||||
or self._built_object is None
|
|
||||||
or not hasattr(self._built_object, "format")
|
|
||||||
):
|
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
# We'll build the prompt with the artifacts
|
# We'll build the prompt with the artifacts
|
||||||
# to show the user what the prompt looks like
|
# to show the user what the prompt looks like
|
||||||
|
|
@ -284,9 +271,7 @@ 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:
|
||||||
if not hasattr(self._built_object, "template") and hasattr(
|
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
|
||||||
self._built_object, "prompt"
|
|
||||||
):
|
|
||||||
template = self._built_object.prompt.template
|
template = self._built_object.prompt.template
|
||||||
else:
|
else:
|
||||||
template = self._built_object.template
|
template = self._built_object.template
|
||||||
|
|
@ -294,11 +279,7 @@ class PromptVertex(Vertex):
|
||||||
if value:
|
if value:
|
||||||
replace_key = "{" + key + "}"
|
replace_key = "{" + key + "}"
|
||||||
template = template.replace(replace_key, value)
|
template = template.replace(replace_key, value)
|
||||||
return (
|
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
|
||||||
template
|
|
||||||
if isinstance(template, str)
|
|
||||||
else f"{self.vertex_type}({template})"
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return str(self._built_object)
|
return str(self._built_object)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import ClassVar, Dict, List, Optional
|
||||||
|
|
||||||
from langchain.agents import types
|
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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
from langflow.template.frontend_node.agents import AgentFrontendNode
|
from langflow.template.frontend_node.agents import AgentFrontendNode
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -15,7 +15,7 @@ from langflow.utils.util import build_template_from_class, build_template_from_m
|
||||||
class AgentCreator(LangChainTypeCreator):
|
class AgentCreator(LangChainTypeCreator):
|
||||||
type_name: str = "agents"
|
type_name: str = "agents"
|
||||||
|
|
||||||
from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"}
|
from_method_nodes: ClassVar[Dict] = {"ZeroShotAgent": "from_llm_and_tools"}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frontend_node_class(self) -> type[AgentFrontendNode]:
|
def frontend_node_class(self) -> type[AgentFrontendNode]:
|
||||||
|
|
@ -42,9 +42,7 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
add_function=True,
|
add_function=True,
|
||||||
method_name=self.from_method_nodes[name],
|
method_name=self.from_method_nodes[name],
|
||||||
)
|
)
|
||||||
return build_template_from_class(
|
return build_template_from_class(name, self.type_to_loader_dict, add_function=True)
|
||||||
name, self.type_to_loader_dict, add_function=True
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Agent not found") from exc
|
raise ValueError("Agent not found") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
@ -56,15 +54,8 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
names = []
|
names = []
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
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() if hasattr(agent, "function_name") else agent.__name__
|
||||||
agent.function_name()
|
if agent_name in settings_service.settings.AGENTS or settings_service.settings.DEV:
|
||||||
if hasattr(agent, "function_name")
|
|
||||||
else agent.__name__
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
agent_name in settings_service.settings.AGENTS
|
|
||||||
or settings_service.settings.DEV
|
|
||||||
):
|
|
||||||
names.append(agent_name)
|
names.append(agent_name)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from langchain.chains.llm import LLMChain
|
from langchain.agents import AgentExecutor, AgentType, Tool, ZeroShotAgent, initialize_agent
|
||||||
from langchain.agents import (
|
|
||||||
AgentExecutor,
|
|
||||||
Tool,
|
|
||||||
ZeroShotAgent,
|
|
||||||
initialize_agent,
|
|
||||||
AgentType,
|
|
||||||
)
|
|
||||||
from langchain.agents.agent_toolkits import (
|
from langchain.agents.agent_toolkits import (
|
||||||
SQLDatabaseToolkit,
|
SQLDatabaseToolkit,
|
||||||
VectorStoreInfo,
|
VectorStoreInfo,
|
||||||
|
|
@ -16,23 +9,18 @@ from langchain.agents.agent_toolkits import (
|
||||||
)
|
)
|
||||||
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
|
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
|
||||||
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
|
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
|
||||||
from langchain.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX
|
|
||||||
from langchain.agents.agent_toolkits.pandas.prompt import (
|
|
||||||
SUFFIX_WITH_DF as PANDAS_SUFFIX,
|
|
||||||
)
|
|
||||||
from langchain.agents.agent_toolkits.sql.prompt import SQL_PREFIX, SQL_SUFFIX
|
from langchain.agents.agent_toolkits.sql.prompt import SQL_PREFIX, SQL_SUFFIX
|
||||||
from langchain.agents.agent_toolkits.vectorstore.prompt import (
|
from langchain.agents.agent_toolkits.vectorstore.prompt import PREFIX as VECTORSTORE_PREFIX
|
||||||
PREFIX as VECTORSTORE_PREFIX,
|
from langchain.agents.agent_toolkits.vectorstore.prompt import ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX
|
||||||
)
|
|
||||||
from langchain.agents.agent_toolkits.vectorstore.prompt import (
|
|
||||||
ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX,
|
|
||||||
)
|
|
||||||
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
|
from langchain.chains.llm import LLMChain
|
||||||
from langchain.memory.chat_memory import BaseChatMemory
|
from langchain.memory.chat_memory import BaseChatMemory
|
||||||
from langchain.sql_database import SQLDatabase
|
from langchain.sql_database import SQLDatabase
|
||||||
from langchain.tools.python.tool import PythonAstREPLTool
|
|
||||||
from langchain.tools.sql_database.prompt import QUERY_CHECKER
|
from langchain.tools.sql_database.prompt import QUERY_CHECKER
|
||||||
|
from langchain_experimental.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX
|
||||||
|
from langchain_experimental.agents.agent_toolkits.pandas.prompt import SUFFIX_WITH_DF as PANDAS_SUFFIX
|
||||||
|
from langchain_experimental.tools.python.tool import PythonAstREPLTool
|
||||||
from langflow.interface.base import CustomAgentExecutor
|
from langflow.interface.base import CustomAgentExecutor
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,7 +41,7 @@ class JsonAgent(CustomAgentExecutor):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel):
|
def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel):
|
||||||
tools = toolkit if isinstance(toolkit, list) else toolkit.get_tools()
|
tools = toolkit if isinstance(toolkit, list) else toolkit.get_tools()
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = list({tool.name for tool in tools})
|
||||||
prompt = ZeroShotAgent.create_prompt(
|
prompt = ZeroShotAgent.create_prompt(
|
||||||
tools,
|
tools,
|
||||||
prefix=JSON_PREFIX,
|
prefix=JSON_PREFIX,
|
||||||
|
|
@ -66,7 +54,8 @@ class JsonAgent(CustomAgentExecutor):
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names # type: ignore
|
llm_chain=llm_chain,
|
||||||
|
allowed_tools=tool_names, # type: ignore
|
||||||
)
|
)
|
||||||
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
||||||
|
|
||||||
|
|
@ -90,11 +79,7 @@ class CSVAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(
|
||||||
cls,
|
cls, path: str, llm: BaseLanguageModel, pandas_kwargs: Optional[dict] = None, **kwargs: Any
|
||||||
path: str,
|
|
||||||
llm: BaseLanguageModel,
|
|
||||||
pandas_kwargs: Optional[dict] = None,
|
|
||||||
**kwargs: Any
|
|
||||||
):
|
):
|
||||||
import pandas as pd # type: ignore
|
import pandas as pd # type: ignore
|
||||||
|
|
||||||
|
|
@ -106,16 +91,18 @@ class CSVAgent(CustomAgentExecutor):
|
||||||
tools,
|
tools,
|
||||||
prefix=PANDAS_PREFIX,
|
prefix=PANDAS_PREFIX,
|
||||||
suffix=PANDAS_SUFFIX,
|
suffix=PANDAS_SUFFIX,
|
||||||
input_variables=["df", "input", "agent_scratchpad"],
|
input_variables=["df_head", "input", "agent_scratchpad"],
|
||||||
)
|
)
|
||||||
partial_prompt = prompt.partial(df=str(df.head()))
|
partial_prompt = prompt.partial(df_head=str(df.head()))
|
||||||
llm_chain = LLMChain(
|
llm_chain = LLMChain(
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=partial_prompt,
|
prompt=partial_prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = list({tool.name for tool in tools})
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
|
allowed_tools=tool_names,
|
||||||
|
**kwargs, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
||||||
|
|
@ -139,9 +126,7 @@ class VectorStoreAgent(CustomAgentExecutor):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any):
|
||||||
cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any
|
|
||||||
):
|
|
||||||
"""Construct a vectorstore agent from an LLM and tools."""
|
"""Construct a vectorstore agent from an LLM and tools."""
|
||||||
|
|
||||||
toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm)
|
toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm)
|
||||||
|
|
@ -152,13 +137,13 @@ class VectorStoreAgent(CustomAgentExecutor):
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = list({tool.name for tool in tools})
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
)
|
allowed_tools=tool_names,
|
||||||
return AgentExecutor.from_agent_and_tools(
|
**kwargs, # type: ignore
|
||||||
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
|
|
||||||
)
|
)
|
||||||
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
@ -179,9 +164,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any):
|
||||||
cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any
|
|
||||||
):
|
|
||||||
"""Construct an SQL agent from an LLM and tools."""
|
"""Construct an SQL agent from an LLM and tools."""
|
||||||
db = SQLDatabase.from_uri(database_uri)
|
db = SQLDatabase.from_uri(database_uri)
|
||||||
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
|
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
|
||||||
|
|
@ -199,9 +182,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
llmchain = LLMChain(
|
llmchain = LLMChain(
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=PromptTemplate(
|
prompt=PromptTemplate(template=QUERY_CHECKER, input_variables=["query", "dialect"]),
|
||||||
template=QUERY_CHECKER, input_variables=["query", "dialect"]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tools = [
|
tools = [
|
||||||
|
|
@ -222,9 +203,11 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools} # type: ignore
|
tool_names = list({tool.name for tool in tools}) # type: ignore
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
|
allowed_tools=tool_names,
|
||||||
|
**kwargs, # type: ignore
|
||||||
)
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
|
|
@ -255,10 +238,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_toolkit_and_llm(
|
def from_toolkit_and_llm(
|
||||||
cls,
|
cls, llm: BaseLanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit, **kwargs: Any
|
||||||
llm: BaseLanguageModel,
|
|
||||||
vectorstoreroutertoolkit: VectorStoreRouterToolkit,
|
|
||||||
**kwargs: Any
|
|
||||||
):
|
):
|
||||||
"""Construct a vector store router agent from an LLM and tools."""
|
"""Construct a vector store router agent from an LLM and tools."""
|
||||||
|
|
||||||
|
|
@ -272,13 +252,13 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = list({tool.name for tool in tools})
|
||||||
agent = ZeroShotAgent(
|
agent = ZeroShotAgent(
|
||||||
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
llm_chain=llm_chain,
|
||||||
)
|
allowed_tools=tool_names,
|
||||||
return AgentExecutor.from_agent_and_tools(
|
**kwargs, # type: ignore
|
||||||
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
|
|
||||||
)
|
)
|
||||||
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
|
@ -30,13 +30,8 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
if self.name_docs_dict is None:
|
if self.name_docs_dict is None:
|
||||||
try:
|
try:
|
||||||
type_settings = getattr(
|
type_settings = getattr(settings_service.settings, self.type_name.upper())
|
||||||
settings_service.settings, self.type_name.upper()
|
self.name_docs_dict = {name: value_dict["documentation"] for name, value_dict in type_settings.items()}
|
||||||
)
|
|
||||||
self.name_docs_dict = {
|
|
||||||
name: value_dict["documentation"]
|
|
||||||
for name, value_dict in type_settings.items()
|
|
||||||
}
|
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
logger.error(f"Error getting settings for {self.type_name}: {exc}")
|
logger.error(f"Error getting settings for {self.type_name}: {exc}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
from typing import Any, Dict, List, Optional, Type
|
from typing import Any, ClassVar, 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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
from langflow.template.frontend_node.chains import ChainFrontendNode
|
from langflow.template.frontend_node.chains import ChainFrontendNode
|
||||||
from loguru import logger
|
from loguru 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
|
from langchain_experimental.sql import SQLDatabaseChain
|
||||||
|
|
||||||
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
# Assuming necessary imports for Field, Template, and FrontendNode classes
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
return ChainFrontendNode
|
return ChainFrontendNode
|
||||||
|
|
||||||
#! We need to find a better solution for this
|
#! We need to find a better solution for this
|
||||||
from_method_nodes = {
|
from_method_nodes: ClassVar[Dict] = {
|
||||||
"ConversationalRetrievalChain": "from_llm",
|
"ConversationalRetrievalChain": "from_llm",
|
||||||
"LLMCheckerChain": "from_llm",
|
"LLMCheckerChain": "from_llm",
|
||||||
"SQLDatabaseChain": "from_llm",
|
"SQLDatabaseChain": "from_llm",
|
||||||
|
|
@ -33,8 +33,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -45,8 +44,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
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_service.settings.CHAINS
|
if name in settings_service.settings.CHAINS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
@ -61,9 +59,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
method_name=self.from_method_nodes[name],
|
method_name=self.from_method_nodes[name],
|
||||||
add_function=True,
|
add_function=True,
|
||||||
)
|
)
|
||||||
return build_template_from_class(
|
return build_template_from_class(name, self.type_to_loader_dict, add_function=True)
|
||||||
name, self.type_to_loader_dict, add_function=True
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(f"Chain {name} not found: {exc}") from exc
|
raise ValueError(f"Chain {name} not found: {exc}") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
@ -73,11 +69,7 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
for _, chain in self.type_to_loader_dict.items():
|
for _, chain in self.type_to_loader_dict.items():
|
||||||
chain_name = (
|
chain_name = chain.function_name() if hasattr(chain, "function_name") else chain.__name__
|
||||||
chain.function_name()
|
|
||||||
if hasattr(chain, "function_name")
|
|
||||||
else chain.__name__
|
|
||||||
)
|
|
||||||
names.append(chain_name)
|
names.append(chain_name)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ from langchain.chains import ConversationChain
|
||||||
from langchain.memory.buffer import ConversationBufferMemory
|
from langchain.memory.buffer import ConversationBufferMemory
|
||||||
from langchain.schema import BaseMemory
|
from langchain.schema import BaseMemory
|
||||||
from langflow.interface.base import CustomChain
|
from langflow.interface.base import CustomChain
|
||||||
from pydantic import Field, root_validator
|
from pydantic.v1 import Field, root_validator
|
||||||
from langchain.chains.question_answering import load_qa_chain
|
from langchain.chains.question_answering import load_qa_chain
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
|
|
@ -41,9 +41,7 @@ class BaseCustomConversationChain(ConversationChain):
|
||||||
values["template"] = values["template"].format(**format_dict)
|
values["template"] = values["template"].format(**format_dict)
|
||||||
|
|
||||||
values["template"] = values["template"]
|
values["template"] = values["template"]
|
||||||
values["input_variables"] = extract_input_variables_from_prompt(
|
values["input_variables"] = extract_input_variables_from_prompt(values["template"])
|
||||||
values["template"]
|
|
||||||
)
|
|
||||||
values["prompt"].template = values["template"]
|
values["prompt"].template = values["template"]
|
||||||
values["prompt"].input_variables = values["input_variables"]
|
values["prompt"].input_variables = values["input_variables"]
|
||||||
return values
|
return values
|
||||||
|
|
@ -54,9 +52,7 @@ class SeriesCharacterChain(BaseCustomConversationChain):
|
||||||
|
|
||||||
character: str
|
character: str
|
||||||
series: str
|
series: str
|
||||||
template: Optional[
|
template: Optional[str] = """I want you to act like {character} from {series}.
|
||||||
str
|
|
||||||
] = """I want you to act like {character} from {series}.
|
|
||||||
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
||||||
You must know all of the knowledge of {character}.
|
You must know all of the knowledge of {character}.
|
||||||
Current conversation:
|
Current conversation:
|
||||||
|
|
@ -71,9 +67,7 @@ Human: {input}
|
||||||
class MidJourneyPromptChain(BaseCustomConversationChain):
|
class MidJourneyPromptChain(BaseCustomConversationChain):
|
||||||
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
|
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
|
||||||
|
|
||||||
template: Optional[
|
template: Optional[str] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
|
||||||
str
|
|
||||||
] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
|
|
||||||
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
|
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
|
||||||
Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.
|
Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible.
|
||||||
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
|
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
|
||||||
|
|
@ -87,9 +81,7 @@ class MidJourneyPromptChain(BaseCustomConversationChain):
|
||||||
|
|
||||||
|
|
||||||
class TimeTravelGuideChain(BaseCustomConversationChain):
|
class TimeTravelGuideChain(BaseCustomConversationChain):
|
||||||
template: Optional[
|
template: Optional[str] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
|
||||||
str
|
|
||||||
] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information.
|
|
||||||
Current conversation:
|
Current conversation:
|
||||||
{history}
|
{history}
|
||||||
Human: {input}
|
Human: {input}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import ast
|
import ast
|
||||||
import inspect
|
import inspect
|
||||||
|
import operator
|
||||||
import traceback
|
import traceback
|
||||||
|
from typing import Any, Dict, List, Type, Union
|
||||||
|
|
||||||
from typing import Dict, Any, List, Type, Union
|
from cachetools import TTLCache, cachedmethod, keys
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails
|
from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,6 +14,19 @@ class CodeSyntaxError(HTTPException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_type():
|
||||||
|
from langflow.field_typing import Data
|
||||||
|
|
||||||
|
return Data
|
||||||
|
|
||||||
|
|
||||||
|
def imports_key(*args, **kwargs):
|
||||||
|
imports = kwargs.pop("imports")
|
||||||
|
key = keys.methodkey(*args, **kwargs)
|
||||||
|
key += tuple(imports)
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
class CodeParser:
|
class CodeParser:
|
||||||
"""
|
"""
|
||||||
A parser for Python source code, extracting code details.
|
A parser for Python source code, extracting code details.
|
||||||
|
|
@ -20,6 +36,7 @@ class CodeParser:
|
||||||
"""
|
"""
|
||||||
Initializes the parser with the provided code.
|
Initializes the parser with the provided code.
|
||||||
"""
|
"""
|
||||||
|
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||||
if isinstance(code, type):
|
if isinstance(code, type):
|
||||||
if not inspect.isclass(code):
|
if not inspect.isclass(code):
|
||||||
raise ValueError("The provided code must be a class.")
|
raise ValueError("The provided code must be a class.")
|
||||||
|
|
@ -65,14 +82,20 @@ class CodeParser:
|
||||||
|
|
||||||
def parse_imports(self, node: Union[ast.Import, ast.ImportFrom]) -> None:
|
def parse_imports(self, node: Union[ast.Import, ast.ImportFrom]) -> None:
|
||||||
"""
|
"""
|
||||||
Extracts "imports" from the code.
|
Extracts "imports" from the code, including aliases.
|
||||||
"""
|
"""
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
self.data["imports"].append(alias.name)
|
if alias.asname:
|
||||||
|
self.data["imports"].append(f"{alias.name} as {alias.asname}")
|
||||||
|
else:
|
||||||
|
self.data["imports"].append(alias.name)
|
||||||
elif isinstance(node, ast.ImportFrom):
|
elif isinstance(node, ast.ImportFrom):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
self.data["imports"].append((node.module, alias.name))
|
if alias.asname:
|
||||||
|
self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}"))
|
||||||
|
else:
|
||||||
|
self.data["imports"].append((node.module, alias.name))
|
||||||
|
|
||||||
def parse_functions(self, node: ast.FunctionDef) -> None:
|
def parse_functions(self, node: ast.FunctionDef) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -89,22 +112,52 @@ class CodeParser:
|
||||||
arg_dict["type"] = ast.unparse(arg.annotation)
|
arg_dict["type"] = ast.unparse(arg.annotation)
|
||||||
return arg_dict
|
return arg_dict
|
||||||
|
|
||||||
|
@cachedmethod(operator.attrgetter("cache"))
|
||||||
|
def construct_eval_env(self, return_type_str: str, imports) -> dict:
|
||||||
|
"""
|
||||||
|
Constructs an evaluation environment with the necessary imports for the return type,
|
||||||
|
taking into account module aliases.
|
||||||
|
"""
|
||||||
|
eval_env: dict = {}
|
||||||
|
for import_entry in imports:
|
||||||
|
if isinstance(import_entry, tuple): # from module import name
|
||||||
|
module, name = import_entry
|
||||||
|
if name in return_type_str:
|
||||||
|
exec(f"import {module}", eval_env)
|
||||||
|
exec(f"from {module} import {name}", eval_env)
|
||||||
|
else: # import module
|
||||||
|
module = import_entry
|
||||||
|
alias = None
|
||||||
|
if " as " in module:
|
||||||
|
module, alias = module.split(" as ")
|
||||||
|
if module in return_type_str or (alias and alias in return_type_str):
|
||||||
|
exec(f"import {module} as {alias if alias else module}", eval_env)
|
||||||
|
return eval_env
|
||||||
|
|
||||||
|
@cachedmethod(cache=operator.attrgetter("cache"))
|
||||||
def parse_callable_details(self, node: ast.FunctionDef) -> Dict[str, Any]:
|
def parse_callable_details(self, node: ast.FunctionDef) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Extracts details from a single function or method node.
|
Extracts details from a single function or method node.
|
||||||
"""
|
"""
|
||||||
|
return_type = None
|
||||||
|
if node.returns:
|
||||||
|
return_type_str = ast.unparse(node.returns)
|
||||||
|
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return_type = eval(return_type_str, eval_env)
|
||||||
|
except NameError:
|
||||||
|
# Handle cases where the type is not found in the constructed environment
|
||||||
|
pass
|
||||||
|
|
||||||
func = CallableCodeDetails(
|
func = CallableCodeDetails(
|
||||||
name=node.name,
|
name=node.name, doc=ast.get_docstring(node), args=[], body=[], return_type=return_type or get_data_type()
|
||||||
doc=ast.get_docstring(node),
|
|
||||||
args=[],
|
|
||||||
body=[],
|
|
||||||
return_type=ast.unparse(node.returns) if node.returns else None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func.args = self.parse_function_args(node)
|
func.args = self.parse_function_args(node)
|
||||||
func.body = self.parse_function_body(node)
|
func.body = self.parse_function_body(node)
|
||||||
|
|
||||||
return func.dict()
|
return func.model_dump()
|
||||||
|
|
||||||
def parse_function_args(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_function_args(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -127,22 +180,14 @@ class CodeParser:
|
||||||
num_defaults = len(node.args.defaults)
|
num_defaults = len(node.args.defaults)
|
||||||
num_missing_defaults = num_args - num_defaults
|
num_missing_defaults = num_args - num_defaults
|
||||||
missing_defaults = [None] * num_missing_defaults
|
missing_defaults = [None] * num_missing_defaults
|
||||||
default_values = [
|
default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
|
||||||
ast.unparse(default).strip("'") if default else None
|
|
||||||
for default in node.args.defaults
|
|
||||||
]
|
|
||||||
# Now check all default values to see if there
|
# Now check all default values to see if there
|
||||||
# are any "None" values in the middle
|
# are any "None" values in the middle
|
||||||
default_values = [
|
default_values = [None if value == "None" else value for value in default_values]
|
||||||
None if value == "None" else value for value in default_values
|
|
||||||
]
|
|
||||||
|
|
||||||
defaults = missing_defaults + default_values
|
defaults = missing_defaults + default_values
|
||||||
|
|
||||||
args = [
|
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)]
|
||||||
self.parse_arg(arg, default)
|
|
||||||
for arg, default in zip(node.args.args, defaults)
|
|
||||||
]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
|
|
@ -160,17 +205,11 @@ class CodeParser:
|
||||||
"""
|
"""
|
||||||
Parses the keyword-only arguments of a function or method node.
|
Parses the keyword-only arguments of a function or method node.
|
||||||
"""
|
"""
|
||||||
kw_defaults = [None] * (
|
kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
|
||||||
len(node.args.kwonlyargs) - len(node.args.kw_defaults)
|
ast.unparse(default) if default else None for default in node.args.kw_defaults
|
||||||
) + [
|
|
||||||
ast.unparse(default) if default else None
|
|
||||||
for default in node.args.kw_defaults
|
|
||||||
]
|
]
|
||||||
|
|
||||||
args = [
|
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)]
|
||||||
self.parse_arg(arg, default)
|
|
||||||
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
|
|
||||||
]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
|
|
@ -240,23 +279,21 @@ class CodeParser:
|
||||||
elif isinstance(stmt, ast.AnnAssign):
|
elif isinstance(stmt, ast.AnnAssign):
|
||||||
if attr := self.parse_ann_assign(stmt):
|
if attr := self.parse_ann_assign(stmt):
|
||||||
class_details.attributes.append(attr)
|
class_details.attributes.append(attr)
|
||||||
elif isinstance(stmt, ast.FunctionDef):
|
elif isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
method, is_init = self.parse_function_def(stmt)
|
method, is_init = self.parse_function_def(stmt)
|
||||||
if is_init:
|
if is_init:
|
||||||
class_details.init = method
|
class_details.init = method
|
||||||
else:
|
else:
|
||||||
class_details.methods.append(method)
|
class_details.methods.append(method)
|
||||||
|
|
||||||
self.data["classes"].append(class_details.dict())
|
self.data["classes"].append(class_details.model_dump())
|
||||||
|
|
||||||
def parse_global_vars(self, node: ast.Assign) -> None:
|
def parse_global_vars(self, node: ast.Assign) -> None:
|
||||||
"""
|
"""
|
||||||
Extracts global variables from the code.
|
Extracts global variables from the code.
|
||||||
"""
|
"""
|
||||||
global_var = {
|
global_var = {
|
||||||
"targets": [
|
"targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
|
||||||
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
|
|
||||||
],
|
|
||||||
"value": ast.unparse(node.value),
|
"value": ast.unparse(node.value),
|
||||||
}
|
}
|
||||||
self.data["global_vars"].append(global_var)
|
self.data["global_vars"].append(global_var)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import ast
|
import ast
|
||||||
from typing import Any, Optional
|
import operator
|
||||||
from pydantic import BaseModel
|
from typing import Any, ClassVar, Optional
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
from langflow.utils import validate
|
from cachetools import TTLCache, cachedmethod
|
||||||
|
from fastapi import HTTPException
|
||||||
from langflow.interface.custom.code_parser import CodeParser
|
from langflow.interface.custom.code_parser import CodeParser
|
||||||
|
from langflow.utils import validate
|
||||||
|
|
||||||
|
|
||||||
class ComponentCodeNullError(HTTPException):
|
class ComponentCodeNullError(HTTPException):
|
||||||
|
|
@ -15,19 +16,20 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Component(BaseModel):
|
class Component:
|
||||||
ERROR_CODE_NULL = "Python code must be provided."
|
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
|
||||||
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL = (
|
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
|
||||||
"The name of the entrypoint function must be provided."
|
|
||||||
)
|
|
||||||
|
|
||||||
code: Optional[str]
|
code: Optional[str] = None
|
||||||
function_entrypoint_name = "build"
|
_function_entrypoint_name: str = "build"
|
||||||
field_config: dict = {}
|
field_config: dict = {}
|
||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
super().__init__(**data)
|
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||||
|
for key, value in data.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
@cachedmethod(cache=operator.attrgetter("cache"))
|
||||||
def get_code_tree(self, code: str):
|
def get_code_tree(self, code: str):
|
||||||
parser = CodeParser(code)
|
parser = CodeParser(code)
|
||||||
return parser.parse_code()
|
return parser.parse_code()
|
||||||
|
|
@ -39,7 +41,7 @@ class Component(BaseModel):
|
||||||
detail={"error": self.ERROR_CODE_NULL, "traceback": ""},
|
detail={"error": self.ERROR_CODE_NULL, "traceback": ""},
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.function_entrypoint_name:
|
if not self._function_entrypoint_name:
|
||||||
raise ComponentFunctionEntrypointNameNullError(
|
raise ComponentFunctionEntrypointNameNullError(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
|
|
@ -48,7 +50,7 @@ class Component(BaseModel):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return validate.create_function(self.code, self.function_entrypoint_name)
|
return validate.create_function(self.code, self._function_entrypoint_name)
|
||||||
|
|
||||||
def build_template_config(self, attributes) -> dict:
|
def build_template_config(self, attributes) -> dict:
|
||||||
template_config = {}
|
template_config = {}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent
|
DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent
|
||||||
|
from typing import Optional, List, Dict, Union
|
||||||
from langflow.field_typing import (
|
from langflow.field_typing import (
|
||||||
Tool,
|
AgentExecutor,
|
||||||
PromptTemplate,
|
|
||||||
Chain,
|
|
||||||
BaseChatMemory,
|
BaseChatMemory,
|
||||||
|
BaseLanguageModel,
|
||||||
BaseLLM,
|
BaseLLM,
|
||||||
BaseLoader,
|
BaseLoader,
|
||||||
BaseMemory,
|
BaseMemory,
|
||||||
BaseOutputParser,
|
BaseOutputParser,
|
||||||
|
BasePromptTemplate,
|
||||||
BaseRetriever,
|
BaseRetriever,
|
||||||
VectorStore,
|
Callable,
|
||||||
Embeddings,
|
Chain,
|
||||||
TextSplitter,
|
ChatPromptTemplate,
|
||||||
Document,
|
|
||||||
AgentExecutor,
|
|
||||||
NestedDict,
|
|
||||||
Data,
|
Data,
|
||||||
|
Document,
|
||||||
|
Embeddings,
|
||||||
|
NestedDict,
|
||||||
|
Object,
|
||||||
|
PromptTemplate,
|
||||||
|
TextSplitter,
|
||||||
|
Tool,
|
||||||
|
VectorStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,47 @@
|
||||||
from typing import Any, Callable, List, Optional, Union
|
import operator
|
||||||
|
from typing import Any, Callable, ClassVar, List, Optional, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from cachetools import TTLCache, cachedmethod
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
|
||||||
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.getters import get_db_service
|
from langflow.interface.custom.utils import (
|
||||||
from langflow.interface.custom.utils import extract_inner_type
|
extract_inner_type_from_generic_alias,
|
||||||
|
extract_union_types_from_generic_alias,
|
||||||
|
)
|
||||||
|
from langflow.services.database.models.flow import Flow
|
||||||
|
from langflow.services.database.utils import session_getter
|
||||||
|
from langflow.services.deps import get_db_service
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
|
|
||||||
from langflow.services.database.utils import session_getter
|
|
||||||
from langflow.services.database.models.flow import Flow
|
|
||||||
from pydantic import Extra
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
class CustomComponent(Component):
|
||||||
class CustomComponent(Component, extra=Extra.allow):
|
display_name: Optional[str] = "Custom Component"
|
||||||
code: Optional[str]
|
description: Optional[str] = "Custom Component"
|
||||||
|
code: Optional[str] = None
|
||||||
field_config: dict = {}
|
field_config: dict = {}
|
||||||
code_class_base_inheritance = "CustomComponent"
|
code_class_base_inheritance: ClassVar[str] = "CustomComponent"
|
||||||
function_entrypoint_name = "build"
|
function_entrypoint_name: ClassVar[str] = "build"
|
||||||
function: Optional[Callable] = None
|
function: Optional[Callable] = None
|
||||||
return_type_valid_list = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
|
|
||||||
repr_value: Optional[Any] = ""
|
repr_value: Optional[Any] = ""
|
||||||
user_id: Optional[Union[UUID, str]] = None
|
user_id: Optional[Union[UUID, str]] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
|
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def return_type_valid_list(self):
|
||||||
|
return list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
|
||||||
|
|
||||||
def custom_repr(self):
|
def custom_repr(self):
|
||||||
|
if self.status:
|
||||||
|
self.repr_value = self.status
|
||||||
if isinstance(self.repr_value, dict):
|
if isinstance(self.repr_value, dict):
|
||||||
return yaml.dump(self.repr_value)
|
return yaml.dump(self.repr_value)
|
||||||
if isinstance(self.repr_value, str):
|
if isinstance(self.repr_value, str):
|
||||||
|
|
@ -53,47 +66,27 @@ 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_in_args(
|
if reader._is_type_hint_used_in_args(type_hint, code) and not reader._is_type_hint_imported(
|
||||||
type_hint, code
|
type_hint, code
|
||||||
) and not reader._is_type_hint_imported(type_hint, 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.",
|
||||||
}
|
}
|
||||||
raise HTTPException(status_code=400, detail=error_detail)
|
raise HTTPException(status_code=400, detail=error_detail)
|
||||||
|
return True
|
||||||
|
|
||||||
def is_check_valid(self) -> bool:
|
def validate(self) -> bool:
|
||||||
return self._class_template_validation(self.code) if self.code else False
|
return self._class_template_validation(self.code) if self.code else False
|
||||||
|
|
||||||
def get_code_tree(self, code: str):
|
def get_code_tree(self, code: str):
|
||||||
return super().get_code_tree(code)
|
return super().get_code_tree(code)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_function_entrypoint_args(self) -> str:
|
def get_function_entrypoint_args(self) -> list:
|
||||||
if not self.code:
|
build_method = self.get_build_method()
|
||||||
return ""
|
if not build_method:
|
||||||
tree = self.get_code_tree(self.code)
|
return []
|
||||||
|
|
||||||
component_classes = [
|
|
||||||
cls
|
|
||||||
for cls in tree["classes"]
|
|
||||||
if self.code_class_base_inheritance in cls["bases"]
|
|
||||||
]
|
|
||||||
if not component_classes:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# Assume the first Component class is the one we're interested in
|
|
||||||
component_class = component_classes[0]
|
|
||||||
build_methods = [
|
|
||||||
method
|
|
||||||
for method in component_class["methods"]
|
|
||||||
if method["name"] == self.function_entrypoint_name
|
|
||||||
]
|
|
||||||
|
|
||||||
if not build_methods:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
build_method = build_methods[0]
|
|
||||||
|
|
||||||
args = build_method["args"]
|
args = build_method["args"]
|
||||||
for arg in args:
|
for arg in args:
|
||||||
|
|
@ -103,68 +96,70 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
detail={
|
detail={
|
||||||
"error": "Type hint Error",
|
"error": "Type hint Error",
|
||||||
"traceback": (
|
"traceback": (
|
||||||
"Prompt type is not supported in the build method."
|
"Prompt type is not supported in the build method." " Try using PromptTemplate instead."
|
||||||
" Try using PromptTemplate instead."
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
elif not arg.get("type"):
|
elif not arg.get("type") and arg.get("name") != "self":
|
||||||
# Set the type to Data
|
# Set the type to Data
|
||||||
arg["type"] = "Data"
|
arg["type"] = "Data"
|
||||||
return args
|
return args
|
||||||
|
|
||||||
@property
|
@cachedmethod(operator.attrgetter("cache"))
|
||||||
def get_function_entrypoint_return_type(self) -> List[str]:
|
def get_build_method(self):
|
||||||
if not self.code:
|
if not self.code:
|
||||||
return []
|
return []
|
||||||
tree = self.get_code_tree(self.code)
|
tree = self.get_code_tree(self.code)
|
||||||
|
|
||||||
component_classes = [
|
component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||||
cls
|
|
||||||
for cls in tree["classes"]
|
|
||||||
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]
|
||||||
build_methods = [
|
build_methods = [
|
||||||
method
|
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
|
||||||
for method in component_class["methods"]
|
|
||||||
if method["name"] == self.function_entrypoint_name
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if not build_methods:
|
if not build_methods:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
build_method = build_methods[0]
|
return build_methods[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_function_entrypoint_return_type(self) -> List[Any]:
|
||||||
|
build_method = self.get_build_method()
|
||||||
|
if not build_method:
|
||||||
|
return build_method
|
||||||
return_type = build_method["return_type"]
|
return_type = build_method["return_type"]
|
||||||
if not return_type:
|
if not return_type:
|
||||||
return []
|
return []
|
||||||
# If list or List is in the return type, then we remove it and return the inner type
|
# If list or List is in the return type, then we remove it and return the inner type
|
||||||
if return_type.startswith("list") or return_type.startswith("List"):
|
if hasattr(return_type, "__origin__") and return_type.__origin__ in [list, List]:
|
||||||
return_type = extract_inner_type(return_type)
|
return_type = extract_inner_type_from_generic_alias(return_type)
|
||||||
|
|
||||||
# If the return type is not a Union, then we just return it as a list
|
# If the return type is not a Union, then we just return it as a list
|
||||||
if "Union" not in return_type:
|
if not hasattr(return_type, "__origin__") or return_type.__origin__ != Union:
|
||||||
return [return_type] if return_type in self.return_type_valid_list else []
|
if isinstance(return_type, list):
|
||||||
|
return return_type
|
||||||
|
return [return_type] # if return_type in self.return_type_valid_list else []
|
||||||
|
|
||||||
# If the return type is a Union, then we need to parse it
|
# If the return type is a Union, then we need to parse itx
|
||||||
return_type = return_type.replace("Union", "").replace("[", "").replace("]", "")
|
return_type = extract_union_types_from_generic_alias(return_type)
|
||||||
return_type = return_type.split(",")
|
# return [item for item in return_type if item in self.return_type_valid_list]
|
||||||
return_type = [item.strip() for item in return_type]
|
return 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):
|
||||||
|
if not self.code:
|
||||||
|
return ""
|
||||||
tree = self.get_code_tree(self.code)
|
tree = self.get_code_tree(self.code)
|
||||||
|
|
||||||
base_name = self.code_class_base_inheritance
|
base_name = self.code_class_base_inheritance
|
||||||
method_name = self.function_entrypoint_name
|
method_name = self.function_entrypoint_name
|
||||||
|
|
||||||
classes = []
|
classes = []
|
||||||
for item in tree.get("classes"):
|
for item in tree.get("classes", []):
|
||||||
if base_name in item["bases"]:
|
if base_name in item["bases"]:
|
||||||
method_names = [method["name"] for method in item["methods"]]
|
method_names = [method["name"] for method in item["methods"]]
|
||||||
if method_name in method_names:
|
if method_name in method_names:
|
||||||
|
|
@ -175,11 +170,13 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def build_template_config(self):
|
def build_template_config(self):
|
||||||
|
if not self.code:
|
||||||
|
return {}
|
||||||
tree = self.get_code_tree(self.code)
|
tree = self.get_code_tree(self.code)
|
||||||
|
|
||||||
attributes = [
|
attributes = [
|
||||||
main_class["attributes"]
|
main_class["attributes"]
|
||||||
for main_class in tree.get("classes")
|
for main_class in tree.get("classes", [])
|
||||||
if main_class["name"] == self.get_main_class_name
|
if main_class["name"] == self.get_main_class_name
|
||||||
]
|
]
|
||||||
# Get just the first item
|
# Get just the first item
|
||||||
|
|
@ -191,9 +188,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
def get_function(self):
|
def get_function(self):
|
||||||
return validate.create_function(self.code, self.function_entrypoint_name)
|
return validate.create_function(self.code, self.function_entrypoint_name)
|
||||||
|
|
||||||
def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any:
|
async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any:
|
||||||
from langflow.processing.process import build_sorted_vertices
|
from langflow.processing.process import build_sorted_vertices, process_tweaks
|
||||||
from langflow.processing.process import process_tweaks
|
|
||||||
|
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with session_getter(db_service) as session:
|
with session_getter(db_service) as session:
|
||||||
|
|
@ -202,7 +198,7 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
if tweaks:
|
if tweaks:
|
||||||
graph_data = process_tweaks(graph_data=graph_data, tweaks=tweaks)
|
graph_data = process_tweaks(graph_data=graph_data, tweaks=tweaks)
|
||||||
return build_sorted_vertices(graph_data)
|
return await build_sorted_vertices(graph_data, self.user_id)
|
||||||
|
|
||||||
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]:
|
||||||
if not self.user_id:
|
if not self.user_id:
|
||||||
|
|
@ -216,7 +212,7 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError("Session is invalid") from e
|
raise ValueError("Session is invalid") from e
|
||||||
|
|
||||||
def get_flow(
|
async def get_flow(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
flow_name: Optional[str] = None,
|
flow_name: Optional[str] = None,
|
||||||
|
|
@ -230,17 +226,13 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
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:
|
||||||
flow = (
|
flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first()
|
||||||
session.query(Flow)
|
|
||||||
.filter(Flow.name == flow_name)
|
|
||||||
.filter(Flow.user_id == self.user_id)
|
|
||||||
).first()
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Either flow_name or flow_id must be provided")
|
raise ValueError("Either flow_name or flow_id must be provided")
|
||||||
|
|
||||||
if not flow:
|
if not flow:
|
||||||
raise ValueError(f"Flow {flow_name or flow_id} not found")
|
raise ValueError(f"Flow {flow_name or flow_id} not found")
|
||||||
return self.load_flow(flow.id, tweaks)
|
return await self.load_flow(flow.id, tweaks)
|
||||||
|
|
||||||
def build(self, *args: Any, **kwargs: Any) -> Any:
|
def build(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@ class DirectoryReader:
|
||||||
for menu in data["menu"]
|
for menu in data["menu"]
|
||||||
]
|
]
|
||||||
filtered = [menu for menu in items if menu["components"]]
|
filtered = [menu for menu in items if menu["components"]]
|
||||||
logger.debug(
|
logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}')
|
||||||
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
|
|
||||||
)
|
|
||||||
return {"menu": filtered}
|
return {"menu": filtered}
|
||||||
|
|
||||||
def validate_code(self, file_content):
|
def validate_code(self, file_content):
|
||||||
|
|
@ -111,9 +109,7 @@ class DirectoryReader:
|
||||||
Walk through the directory path and return a list of all .py files.
|
Walk through the directory path and return a list of all .py files.
|
||||||
"""
|
"""
|
||||||
if not (safe_path := self.get_safe_path()):
|
if not (safe_path := self.get_safe_path()):
|
||||||
raise CustomComponentPathValueError(
|
raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.")
|
||||||
f"The path needs to start with '{self.base_path}'."
|
|
||||||
)
|
|
||||||
|
|
||||||
file_list = []
|
file_list = []
|
||||||
for root, _, files in os.walk(safe_path):
|
for root, _, files in os.walk(safe_path):
|
||||||
|
|
@ -158,9 +154,7 @@ class DirectoryReader:
|
||||||
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:
|
except SyntaxError:
|
||||||
# Returns False if the code is not valid Python
|
# Returns False if the code is not valid Python
|
||||||
|
|
@ -178,16 +172,14 @@ class DirectoryReader:
|
||||||
and annotation.value.id == type_hint_name
|
and annotation.value.id == type_hint_name
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_type_hint_used_but_not_imported(
|
def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
|
||||||
self, type_hint_name: str, code: str
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Check if a type hint is used but not imported in the given code.
|
Check if a type hint is used but not imported in the given code.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._is_type_hint_used_in_args(
|
return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
|
||||||
type_hint_name, code
|
type_hint_name, code
|
||||||
) and not self._is_type_hint_imported(type_hint_name, code)
|
)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# Returns True if there's something wrong with the code
|
# Returns True if there's something wrong with the code
|
||||||
# TODO : Find a better way to handle this
|
# TODO : Find a better way to handle this
|
||||||
|
|
@ -208,9 +200,9 @@ 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_in_args(
|
elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
|
||||||
"Optional", file_content
|
"Optional", file_content
|
||||||
) and not self._is_type_hint_imported("Optional", file_content):
|
):
|
||||||
return (
|
return (
|
||||||
False,
|
False,
|
||||||
"Type hint 'Optional' is used but not imported in the code.",
|
"Type hint 'Optional' is used but not imported in the code.",
|
||||||
|
|
@ -226,9 +218,7 @@ class DirectoryReader:
|
||||||
from the .py files in the directory.
|
from the .py files in the directory.
|
||||||
"""
|
"""
|
||||||
response = {"menu": []}
|
response = {"menu": []}
|
||||||
logger.debug(
|
logger.debug("-------------------- Building component menu list --------------------")
|
||||||
"-------------------- 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))
|
||||||
|
|
@ -248,9 +238,7 @@ class DirectoryReader:
|
||||||
|
|
||||||
# first check if it's already CamelCase
|
# first check if it's already CamelCase
|
||||||
if "_" in component_name:
|
if "_" in component_name:
|
||||||
component_name_camelcase = " ".join(
|
component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
|
||||||
word.title() for word in component_name.split("_")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
component_name_camelcase = component_name
|
component_name_camelcase = component_name
|
||||||
|
|
||||||
|
|
@ -266,7 +254,5 @@ class DirectoryReader:
|
||||||
logger.debug(f"Component info: {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(
|
logger.debug("-------------------- Component menu list built --------------------")
|
||||||
"-------------------- Component menu list built --------------------"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class ClassCodeDetails(BaseModel):
|
class ClassCodeDetails(BaseModel):
|
||||||
"""
|
"""
|
||||||
A dataclass for storing details about a class.
|
A dataclass for storing details about a class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
doc: Optional[str]
|
doc: Optional[str] = None
|
||||||
bases: list
|
bases: list
|
||||||
attributes: list
|
attributes: list
|
||||||
methods: list
|
methods: list
|
||||||
|
|
@ -23,7 +22,7 @@ class CallableCodeDetails(BaseModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
doc: Optional[str]
|
doc: Optional[str] = None
|
||||||
args: list
|
args: list
|
||||||
body: list
|
body: list
|
||||||
return_type: Optional[str]
|
return_type: Optional[Any] = None
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import re
|
import re
|
||||||
|
from types import GenericAlias
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def extract_inner_type(return_type: str) -> str:
|
def extract_inner_type(return_type: str) -> str:
|
||||||
|
|
@ -8,3 +10,31 @@ def extract_inner_type(return_type: str) -> str:
|
||||||
if match := re.match(r"list\[(.*)\]", return_type, re.IGNORECASE):
|
if match := re.match(r"list\[(.*)\]", return_type, re.IGNORECASE):
|
||||||
return match[1]
|
return match[1]
|
||||||
return return_type
|
return return_type
|
||||||
|
|
||||||
|
|
||||||
|
def extract_inner_type_from_generic_alias(return_type: GenericAlias) -> Any:
|
||||||
|
"""
|
||||||
|
Extracts the inner type from a type hint that is a list.
|
||||||
|
"""
|
||||||
|
if return_type.__origin__ == list:
|
||||||
|
return list(return_type.__args__)
|
||||||
|
|
||||||
|
return return_type
|
||||||
|
|
||||||
|
|
||||||
|
def extract_union_types_from_generic_alias(return_type: GenericAlias) -> list:
|
||||||
|
"""
|
||||||
|
Extracts the inner type from a type hint that is a Union.
|
||||||
|
"""
|
||||||
|
return list(return_type.__args__)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_union_types(return_type: str) -> list[str]:
|
||||||
|
"""
|
||||||
|
Extracts the inner type from a type hint that is a list.
|
||||||
|
"""
|
||||||
|
# If the return type is a Union, then we need to parse it
|
||||||
|
return_type = return_type.replace("Union", "").replace("[", "").replace("]", "")
|
||||||
|
return_types = return_type.split(",")
|
||||||
|
return_types = [item.strip() for item in return_types]
|
||||||
|
return return_types
|
||||||
|
|
|
||||||
|
|
@ -46,34 +46,26 @@ toolkit_type_to_cls_dict: dict[str, Any] = {
|
||||||
|
|
||||||
# Memories
|
# Memories
|
||||||
memory_type_to_cls_dict: dict[str, Any] = {
|
memory_type_to_cls_dict: dict[str, Any] = {
|
||||||
memory_name: import_class(f"langchain.memory.{memory_name}")
|
memory_name: import_class(f"langchain.memory.{memory_name}") for memory_name in memory.__all__
|
||||||
for memory_name in memory.__all__
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wrappers
|
# Wrappers
|
||||||
wrapper_type_to_cls_dict: dict[str, Any] = {
|
wrapper_type_to_cls_dict: dict[str, Any] = {wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]}
|
||||||
wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Embeddings
|
# Embeddings
|
||||||
embedding_type_to_cls_dict: dict[str, Any] = {
|
embedding_type_to_cls_dict: dict[str, Any] = {
|
||||||
embedding_name: import_class(f"langchain.embeddings.{embedding_name}")
|
embedding_name: import_class(f"langchain.embeddings.{embedding_name}") for embedding_name in embeddings.__all__
|
||||||
for embedding_name in embeddings.__all__
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Document Loaders
|
# Document Loaders
|
||||||
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
||||||
documentloader_name: import_class(
|
documentloader_name: import_class(f"langchain.document_loaders.{documentloader_name}")
|
||||||
f"langchain.document_loaders.{documentloader_name}"
|
|
||||||
)
|
|
||||||
for documentloader_name in document_loaders.__all__
|
for documentloader_name in document_loaders.__all__
|
||||||
}
|
}
|
||||||
|
|
||||||
# Text Splitters
|
# Text Splitters
|
||||||
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
|
textsplitter_type_to_cls_dict: dict[str, Any] = dict(inspect.getmembers(text_splitter, inspect.isclass))
|
||||||
inspect.getmembers(text_splitter, inspect.isclass)
|
|
||||||
)
|
|
||||||
|
|
||||||
# merge CUSTOM_AGENTS and CUSTOM_CHAINS
|
# merge CUSTOM_AGENTS and CUSTOM_CHAINS
|
||||||
CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore
|
CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
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.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -35,8 +35,7 @@ class DocumentLoaderCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
documentloader.__name__
|
documentloader.__name__
|
||||||
for documentloader in self.type_to_loader_dict.values()
|
for documentloader in self.type_to_loader_dict.values()
|
||||||
if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS
|
if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -37,8 +37,7 @@ class EmbeddingCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
embedding.__name__
|
embedding.__name__
|
||||||
for embedding in self.type_to_loader_dict.values()
|
for embedding in self.type_to_loader_dict.values()
|
||||||
if embedding.__name__ in settings_service.settings.EMBEDDINGS
|
if embedding.__name__ in settings_service.settings.EMBEDDINGS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@
|
||||||
import importlib
|
import importlib
|
||||||
from typing import Any, Type
|
from typing import Any, Type
|
||||||
|
|
||||||
from langchain.prompts import PromptTemplate
|
|
||||||
from langchain.agents import Agent
|
from langchain.agents import Agent
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.chat_models.base import BaseChatModel
|
from langchain.chat_models.base import BaseChatModel
|
||||||
|
from langchain.prompts import PromptTemplate
|
||||||
from langchain.tools import BaseTool
|
from langchain.tools import BaseTool
|
||||||
from langflow.interface.custom.custom_component import CustomComponent
|
from langflow.interface.custom.custom_component import CustomComponent
|
||||||
from langflow.utils import validate
|
|
||||||
from langflow.interface.wrappers.base import wrapper_creator
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
|
from langflow.utils import validate
|
||||||
|
|
||||||
|
|
||||||
def import_module(module_path: str) -> Any:
|
def import_module(module_path: str) -> Any:
|
||||||
|
|
@ -104,10 +104,7 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]:
|
||||||
|
|
||||||
def import_wrapper(wrapper: str) -> Any:
|
def import_wrapper(wrapper: str) -> Any:
|
||||||
"""Import wrapper from wrapper name"""
|
"""Import wrapper from wrapper name"""
|
||||||
if (
|
if isinstance(wrapper_creator.type_dict, dict) and wrapper in wrapper_creator.type_dict:
|
||||||
isinstance(wrapper_creator.type_dict, dict)
|
|
||||||
and wrapper in wrapper_creator.type_dict
|
|
||||||
):
|
|
||||||
return wrapper_creator.type_dict.get(wrapper)
|
return wrapper_creator.type_dict.get(wrapper)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -183,6 +180,7 @@ def get_function(code):
|
||||||
return validate.create_function(code, function_name)
|
return validate.create_function(code, function_name)
|
||||||
|
|
||||||
|
|
||||||
def get_function_custom(code):
|
def eval_custom_component_code(code: str) -> Type[CustomComponent]:
|
||||||
|
"""Evaluate custom component code"""
|
||||||
class_name = validate.extract_class_name(code)
|
class_name = validate.extract_class_name(code)
|
||||||
return validate.create_class(code, class_name)
|
return validate.create_class(code, class_name)
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ def initialize_vertexai(class_object, params):
|
||||||
if credentials_path := params.get("credentials"):
|
if credentials_path := params.get("credentials"):
|
||||||
from google.oauth2 import service_account # type: ignore
|
from google.oauth2 import service_account # type: ignore
|
||||||
|
|
||||||
credentials_object = service_account.Credentials.from_service_account_file(
|
credentials_object = service_account.Credentials.from_service_account_file(filename=credentials_path)
|
||||||
filename=credentials_path
|
|
||||||
)
|
|
||||||
params["credentials"] = credentials_object
|
params["credentials"] = credentials_object
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,29 @@
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
|
||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
from typing import Any, Callable, Dict, Sequence, Type, TYPE_CHECKING
|
|
||||||
from langchain.schema import Document
|
|
||||||
from langchain.agents import agent as agent_module
|
from langchain.agents import agent as agent_module
|
||||||
from langchain.agents.agent import AgentExecutor
|
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 langchain.chains.base import Chain
|
||||||
from langflow.interface.initialize.utils import (
|
from langchain.document_loaders.base import BaseLoader
|
||||||
handle_format_kwargs,
|
from langchain.schema import Document
|
||||||
handle_node_type,
|
from langchain.vectorstores.base import VectorStore
|
||||||
handle_partial_variables,
|
|
||||||
)
|
|
||||||
|
|
||||||
from langflow.interface.initialize.vector_store import vecstore_initializer
|
|
||||||
|
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
from langflow.interface.importing.utils import (
|
|
||||||
get_function,
|
|
||||||
get_function_custom,
|
|
||||||
import_by_type,
|
|
||||||
)
|
|
||||||
from langflow.interface.custom_lists import CUSTOM_NODES
|
from langflow.interface.custom_lists import CUSTOM_NODES
|
||||||
from langflow.interface.agents.base import agent_creator
|
from langflow.interface.importing.utils import eval_custom_component_code, get_function, import_by_type
|
||||||
from langflow.interface.toolkits.base import toolkits_creator
|
from langflow.interface.initialize.llm import initialize_vertexai
|
||||||
from langflow.interface.chains.base import chain_creator
|
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.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.wrappers.base import wrapper_creator
|
from langflow.interface.toolkits.base import toolkits_creator
|
||||||
from langflow.interface.utils import load_file_into_dict
|
from langflow.interface.utils import load_file_into_dict
|
||||||
|
from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
from langchain.chains.base import Chain
|
|
||||||
from langchain.vectorstores.base import VectorStore
|
|
||||||
from langchain.document_loaders.base import BaseLoader
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from langflow import CustomComponent
|
from langflow import CustomComponent
|
||||||
|
|
@ -44,15 +33,10 @@ def build_vertex_in_params(params: Dict) -> Dict:
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
|
|
||||||
# If any of the values in params is a Vertex, we will build it
|
# If any of the values in params is a Vertex, we will build it
|
||||||
return {
|
return {key: value.build() if isinstance(value, Vertex) else value for key, value in params.items()}
|
||||||
key: value.build() if isinstance(value, Vertex) else value
|
|
||||||
for key, value in params.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_class(
|
async def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None) -> Any:
|
||||||
node_type: str, base_type: str, params: Dict, user_id=None
|
|
||||||
) -> Any:
|
|
||||||
"""Instantiate class from module type and key, and params"""
|
"""Instantiate class from module type and key, and params"""
|
||||||
params = convert_params_to_sets(params)
|
params = convert_params_to_sets(params)
|
||||||
params = convert_kwargs(params)
|
params = convert_kwargs(params)
|
||||||
|
|
@ -64,9 +48,7 @@ def instantiate_class(
|
||||||
return custom_node(**params)
|
return custom_node(**params)
|
||||||
logger.debug(f"Instantiating {node_type} of type {base_type}")
|
logger.debug(f"Instantiating {node_type} of type {base_type}")
|
||||||
class_object = import_by_type(_type=base_type, name=node_type)
|
class_object = import_by_type(_type=base_type, name=node_type)
|
||||||
return instantiate_based_on_type(
|
return await instantiate_based_on_type(class_object, base_type, node_type, params, user_id=user_id)
|
||||||
class_object, base_type, node_type, params, user_id=user_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_params_to_sets(params):
|
def convert_params_to_sets(params):
|
||||||
|
|
@ -93,7 +75,7 @@ def convert_kwargs(params):
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
def instantiate_based_on_type(class_object, base_type, node_type, params, user_id):
|
async def instantiate_based_on_type(class_object, base_type, node_type, params, user_id):
|
||||||
if base_type == "agents":
|
if base_type == "agents":
|
||||||
return instantiate_agent(node_type, class_object, params)
|
return instantiate_agent(node_type, class_object, params)
|
||||||
elif base_type == "prompts":
|
elif base_type == "prompts":
|
||||||
|
|
@ -127,20 +109,28 @@ def instantiate_based_on_type(class_object, base_type, node_type, params, user_i
|
||||||
elif base_type == "memory":
|
elif base_type == "memory":
|
||||||
return instantiate_memory(node_type, class_object, params)
|
return instantiate_memory(node_type, class_object, params)
|
||||||
elif base_type == "custom_components":
|
elif base_type == "custom_components":
|
||||||
return instantiate_custom_component(node_type, class_object, params, user_id)
|
return await instantiate_custom_component(node_type, class_object, params, user_id)
|
||||||
elif base_type == "wrappers":
|
elif base_type == "wrappers":
|
||||||
return instantiate_wrapper(node_type, class_object, params)
|
return instantiate_wrapper(node_type, class_object, params)
|
||||||
else:
|
else:
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
def instantiate_custom_component(node_type, class_object, params, user_id):
|
async def instantiate_custom_component(node_type, class_object, params, user_id):
|
||||||
# we need to make a copy of the params because we will be
|
|
||||||
# modifying it
|
|
||||||
params_copy = params.copy()
|
params_copy = params.copy()
|
||||||
class_object: "CustomComponent" = get_function_custom(params_copy.pop("code"))
|
class_object: "CustomComponent" = eval_custom_component_code(params_copy.pop("code"))
|
||||||
custom_component = class_object(user_id=user_id)
|
custom_component = class_object(user_id=user_id)
|
||||||
built_object = custom_component.build(**params_copy)
|
|
||||||
|
# Determine if the build method is asynchronous
|
||||||
|
is_async = inspect.iscoroutinefunction(custom_component.build)
|
||||||
|
|
||||||
|
if is_async:
|
||||||
|
# Await the build method directly if it's async
|
||||||
|
built_object = await custom_component.build(**params_copy)
|
||||||
|
else:
|
||||||
|
# Call the build method directly if it's sync
|
||||||
|
built_object = custom_component.build(**params_copy)
|
||||||
|
|
||||||
return built_object, {"repr": custom_component.custom_repr()}
|
return built_object, {"repr": custom_component.custom_repr()}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -194,9 +184,7 @@ def instantiate_memory(node_type, class_object, params):
|
||||||
# I want to catch a specific attribute error that happens
|
# I want to catch a specific attribute error that happens
|
||||||
# when the object does not have a cursor attribute
|
# when the object does not have a cursor attribute
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if "object has no attribute 'cursor'" in str(
|
if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
|
||||||
exc
|
|
||||||
) or 'object has no field "conn"' in str(exc):
|
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
(
|
(
|
||||||
"Failed to build connection to database."
|
"Failed to build connection to database."
|
||||||
|
|
@ -218,6 +206,8 @@ def instantiate_retriever(node_type, class_object, params):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
|
def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
|
||||||
|
from langflow.interface.chains.base import chain_creator
|
||||||
|
|
||||||
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
||||||
params["retriever"] = params["retriever"].as_retriever()
|
params["retriever"] = params["retriever"].as_retriever()
|
||||||
if node_type in chain_creator.from_method_nodes:
|
if node_type in chain_creator.from_method_nodes:
|
||||||
|
|
@ -230,14 +220,14 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
|
def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
|
||||||
|
from langflow.interface.agents.base import agent_creator
|
||||||
|
|
||||||
if node_type in agent_creator.from_method_nodes:
|
if node_type in agent_creator.from_method_nodes:
|
||||||
method = agent_creator.from_method_nodes[node_type]
|
method = agent_creator.from_method_nodes[node_type]
|
||||||
if class_method := getattr(class_object, method, None):
|
if class_method := getattr(class_object, method, None):
|
||||||
agent = class_method(**params)
|
agent = class_method(**params)
|
||||||
tools = params.get("tools", [])
|
tools = params.get("tools", [])
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
|
||||||
agent=agent, tools=tools, handle_parsing_errors=True
|
|
||||||
)
|
|
||||||
return load_agent_executor(class_object, params)
|
return load_agent_executor(class_object, params)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -287,26 +277,25 @@ def instantiate_embedding(node_type, class_object, params: Dict):
|
||||||
if "VertexAI" in node_type:
|
if "VertexAI" in node_type:
|
||||||
return initialize_vertexai(class_object=class_object, params=params)
|
return initialize_vertexai(class_object=class_object, params=params)
|
||||||
|
|
||||||
|
if "OpenAIEmbedding" in node_type:
|
||||||
|
params["disallowed_special"] = ()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
params = {
|
params = {key: value for key, value in params.items() if key in class_object.model_fields}
|
||||||
key: value
|
|
||||||
for key, value in params.items()
|
|
||||||
if key in class_object.__fields__
|
|
||||||
}
|
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
search_kwargs = params.pop("search_kwargs", {})
|
search_kwargs = params.pop("search_kwargs", {})
|
||||||
|
if search_kwargs == {"yourkey": "value"}:
|
||||||
|
search_kwargs = {}
|
||||||
# clean up docs or texts to have only documents
|
# clean up docs or texts to have only documents
|
||||||
if "texts" in params:
|
if "texts" in params:
|
||||||
params["documents"] = params.pop("texts")
|
params["documents"] = params.pop("texts")
|
||||||
if "documents" in params:
|
if "documents" in params:
|
||||||
params["documents"] = [
|
params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
|
||||||
doc for doc in params["documents"] if isinstance(doc, Document)
|
|
||||||
]
|
|
||||||
if initializer := vecstore_initializer.get(class_object.__name__):
|
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||||
vecstore = initializer(class_object, params)
|
vecstore = initializer(class_object, params)
|
||||||
else:
|
else:
|
||||||
|
|
@ -321,9 +310,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
return vecstore
|
return vecstore
|
||||||
|
|
||||||
|
|
||||||
def instantiate_documentloader(
|
def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
|
||||||
node_type: str, class_object: Type[BaseLoader], params: Dict
|
|
||||||
):
|
|
||||||
if "file_filter" in params:
|
if "file_filter" in params:
|
||||||
# file_filter will be a string but we need a function
|
# file_filter will be a string but we need a function
|
||||||
# that will be used to filter the files using file_filter
|
# that will be used to filter the files using file_filter
|
||||||
|
|
@ -332,17 +319,13 @@ def instantiate_documentloader(
|
||||||
# in x and if it is, we will return True
|
# in x and if it is, we will return True
|
||||||
file_filter = params.pop("file_filter")
|
file_filter = params.pop("file_filter")
|
||||||
extensions = file_filter.split(",")
|
extensions = file_filter.split(",")
|
||||||
params["file_filter"] = lambda x: any(
|
params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
|
||||||
extension.strip() in x for extension in extensions
|
|
||||||
)
|
|
||||||
metadata = params.pop("metadata", None)
|
metadata = params.pop("metadata", None)
|
||||||
if metadata and isinstance(metadata, str):
|
if metadata and isinstance(metadata, str):
|
||||||
try:
|
try:
|
||||||
metadata = orjson.loads(metadata)
|
metadata = orjson.loads(metadata)
|
||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
raise ValueError(
|
raise ValueError("The metadata you provided is not a valid JSON string.") from exc
|
||||||
"The metadata you provided is not a valid JSON string."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
if node_type == "WebBaseLoader":
|
if node_type == "WebBaseLoader":
|
||||||
if web_path := params.pop("web_path", None):
|
if web_path := params.pop("web_path", None):
|
||||||
|
|
@ -375,16 +358,12 @@ def instantiate_textsplitter(
|
||||||
"Try changing the chunk_size of the Text Splitter."
|
"Try changing the chunk_size of the Text Splitter."
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
if (
|
if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
|
||||||
"separator_type" in params and params["separator_type"] == "Text"
|
|
||||||
) or "separator_type" not in params:
|
|
||||||
params.pop("separator_type", None)
|
params.pop("separator_type", None)
|
||||||
# separators might come in as an escaped string like \\n
|
# separators might come in as an escaped string like \\n
|
||||||
# so we need to convert it to a string
|
# so we need to convert it to a string
|
||||||
if "separators" in params:
|
if "separators" in params:
|
||||||
params["separators"] = (
|
params["separators"] = params["separators"].encode().decode("unicode-escape")
|
||||||
params["separators"].encode().decode("unicode-escape")
|
|
||||||
)
|
|
||||||
text_splitter = class_object(**params)
|
text_splitter = class_object(**params)
|
||||||
else:
|
else:
|
||||||
from langchain.text_splitter import Language
|
from langchain.text_splitter import Language
|
||||||
|
|
@ -411,8 +390,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
tools = [
|
tools = [
|
||||||
tool
|
tool
|
||||||
for tool in nodes
|
for tool in nodes
|
||||||
if tool["type"] != "chatOutputNode"
|
if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
|
||||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
|
||||||
]
|
]
|
||||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||||
break
|
break
|
||||||
|
|
@ -426,9 +404,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
|
||||||
# agent has hidden args for memory. might need to be support
|
# agent has hidden args for memory. might need to be support
|
||||||
# memory = params["memory"]
|
# memory = params["memory"]
|
||||||
# if allowed_tools is not a list or set, make it a list
|
# if allowed_tools is not a list or set, make it a list
|
||||||
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
|
||||||
allowed_tools, BaseTool
|
|
||||||
):
|
|
||||||
allowed_tools = [allowed_tools]
|
allowed_tools = [allowed_tools]
|
||||||
tool_names = [tool.name for tool in allowed_tools]
|
tool_names = [tool.name for tool in allowed_tools]
|
||||||
# Agent class requires an output_parser but Agent classes
|
# Agent class requires an output_parser but Agent classes
|
||||||
|
|
@ -456,10 +432,7 @@ def build_prompt_template(prompt, tools):
|
||||||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||||
|
|
||||||
tool_strings = "\n".join(
|
tool_strings = "\n".join(
|
||||||
[
|
[f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools]
|
||||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
|
||||||
for tool in tools
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,7 @@ def check_tools_in_params(params: Dict):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_from_template(class_object, params: Dict):
|
def instantiate_from_template(class_object, params: Dict):
|
||||||
from_template_params = {
|
from_template_params = {"template": params.pop("prompt", params.pop("template", ""))}
|
||||||
"template": params.pop("prompt", params.pop("template", ""))
|
|
||||||
}
|
|
||||||
if not from_template_params.get("template"):
|
if not from_template_params.get("template"):
|
||||||
raise ValueError("Prompt template is required")
|
raise ValueError("Prompt template is required")
|
||||||
return class_object.from_template(**from_template_params)
|
return class_object.from_template(**from_template_params)
|
||||||
|
|
@ -48,9 +46,7 @@ def handle_format_kwargs(prompt, params: Dict):
|
||||||
|
|
||||||
def handle_partial_variables(prompt, format_kwargs: Dict):
|
def handle_partial_variables(prompt, format_kwargs: Dict):
|
||||||
partial_variables = format_kwargs.copy()
|
partial_variables = format_kwargs.copy()
|
||||||
partial_variables = {
|
partial_variables = {key: value for key, value in partial_variables.items() if value}
|
||||||
key: value for key, value in partial_variables.items() if value
|
|
||||||
}
|
|
||||||
# Remove handle_keys otherwise LangChain raises an error
|
# Remove handle_keys otherwise LangChain raises an error
|
||||||
partial_variables.pop("handle_keys", None)
|
partial_variables.pop("handle_keys", None)
|
||||||
if partial_variables and hasattr(prompt, "partial"):
|
if partial_variables and hasattr(prompt, "partial"):
|
||||||
|
|
@ -62,9 +58,7 @@ 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):
|
||||||
format_kwargs[input_variable] = variable
|
format_kwargs[input_variable] = variable
|
||||||
elif isinstance(variable, BaseOutputParser) and hasattr(
|
elif isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions"):
|
||||||
variable, "get_format_instructions"
|
|
||||||
):
|
|
||||||
format_kwargs[input_variable] = variable.get_format_instructions()
|
format_kwargs[input_variable] = variable.get_format_instructions()
|
||||||
elif is_instance_of_list_or_document(variable):
|
elif is_instance_of_list_or_document(variable):
|
||||||
format_kwargs = format_document(variable, input_variable, format_kwargs)
|
format_kwargs = format_document(variable, input_variable, format_kwargs)
|
||||||
|
|
@ -107,8 +101,7 @@ def try_to_load_json(content):
|
||||||
|
|
||||||
def needs_handle_keys(variable):
|
def needs_handle_keys(variable):
|
||||||
return is_instance_of_list_or_document(variable) or (
|
return is_instance_of_list_or_document(variable) or (
|
||||||
isinstance(variable, BaseOutputParser)
|
isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions")
|
||||||
and hasattr(variable, "get_format_instructions")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@ import orjson
|
||||||
def docs_in_params(params: dict) -> bool:
|
def docs_in_params(params: dict) -> bool:
|
||||||
"""Check if params has documents OR texts and one of them is not an empty list,
|
"""Check if params has documents OR texts and one of them is not an empty list,
|
||||||
If any of them is not an empty list, return True, else return False"""
|
If any of them is not an empty list, return True, else return False"""
|
||||||
return ("documents" in params and params["documents"]) or (
|
return ("documents" in params and params["documents"]) or ("texts" in params and params["texts"])
|
||||||
"texts" in params and params["texts"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
||||||
|
|
@ -31,9 +29,7 @@ def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dic
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
import certifi
|
import certifi
|
||||||
|
|
||||||
client: MongoClient = MongoClient(
|
client: MongoClient = MongoClient(MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where())
|
||||||
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
|
|
||||||
)
|
|
||||||
db_name = params.pop("db_name", None)
|
db_name = params.pop("db_name", None)
|
||||||
collection_name = params.pop("collection_name", None)
|
collection_name = params.pop("collection_name", None)
|
||||||
if not db_name or not collection_name:
|
if not db_name or not collection_name:
|
||||||
|
|
@ -141,9 +137,7 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
pinecone_env = os.getenv("PINECONE_ENV")
|
pinecone_env = os.getenv("PINECONE_ENV")
|
||||||
|
|
||||||
if pinecone_api_key is None or pinecone_env is None:
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
raise ValueError(
|
raise ValueError("Pinecone API key and environment must be provided in the params")
|
||||||
"Pinecone API key and environment must be provided in the params"
|
|
||||||
)
|
|
||||||
|
|
||||||
# initialize pinecone
|
# initialize pinecone
|
||||||
pinecone.init(
|
pinecone.init(
|
||||||
|
|
@ -177,26 +171,20 @@ def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
import chromadb # type: ignore
|
import chromadb # type: ignore
|
||||||
|
|
||||||
settings_params = {
|
settings_params = {
|
||||||
key: params[key]
|
key: params[key] for key, value_ in params.items() if key.startswith("chroma_server_") and value_
|
||||||
for key, value_ in params.items()
|
|
||||||
if key.startswith("chroma_server_") and value_
|
|
||||||
}
|
}
|
||||||
chroma_settings = chromadb.config.Settings(**settings_params)
|
chroma_settings = chromadb.config.Settings(**settings_params)
|
||||||
params["client_settings"] = chroma_settings
|
params["client_settings"] = chroma_settings
|
||||||
else:
|
else:
|
||||||
# remove all chroma_server_ keys from params
|
# remove all chroma_server_ keys from params
|
||||||
params = {
|
params = {key: value for key, value in params.items() if not key.startswith("chroma_server_")}
|
||||||
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)
|
||||||
params.pop("texts", None)
|
params.pop("texts", None)
|
||||||
params["embedding_function"] = params.pop("embedding")
|
params["embedding_function"] = params.pop("embedding")
|
||||||
chromadb = class_object(**params)
|
chromadb_instance = class_object(**params)
|
||||||
else:
|
else:
|
||||||
if "texts" in params:
|
if "texts" in params:
|
||||||
params["documents"] = params.pop("texts")
|
params["documents"] = params.pop("texts")
|
||||||
|
|
@ -211,10 +199,10 @@ def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
if value is None:
|
if value is None:
|
||||||
doc.metadata[key] = ""
|
doc.metadata[key] = ""
|
||||||
|
|
||||||
chromadb = class_object.from_documents(**params)
|
chromadb_instance = class_object.from_documents(**params)
|
||||||
if persist:
|
if persist:
|
||||||
chromadb.persist()
|
chromadb_instance.persist()
|
||||||
return chromadb
|
return chromadb_instance
|
||||||
|
|
||||||
|
|
||||||
def initialize_qdrant(class_object: Type[Qdrant], params: dict):
|
def initialize_qdrant(class_object: Type[Qdrant], params: dict):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from langflow.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
from langflow.utils.lazy_load import LazyLoadDictBase
|
from langflow.utils.lazy_load import LazyLoadDictBase
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
from langflow.template.frontend_node.llms import LLMFrontendNode
|
from langflow.template.frontend_node.llms import LLMFrontendNode
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -38,8 +38,7 @@ class LLMCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
llm.__name__
|
llm.__name__
|
||||||
for llm in self.type_to_loader_dict.values()
|
for llm in self.type_to_loader_dict.values()
|
||||||
if llm.__name__ in settings_service.settings.LLMS
|
if llm.__name__ in settings_service.settings.LLMS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import ClassVar, 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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -14,7 +14,7 @@ from langflow.custom.customs import get_custom_nodes
|
||||||
class MemoryCreator(LangChainTypeCreator):
|
class MemoryCreator(LangChainTypeCreator):
|
||||||
type_name: str = "memories"
|
type_name: str = "memories"
|
||||||
|
|
||||||
from_method_nodes = {
|
from_method_nodes: ClassVar[Dict] = {
|
||||||
"ZepChatMessageHistory": "__init__",
|
"ZepChatMessageHistory": "__init__",
|
||||||
"SQLiteEntityStore": "__init__",
|
"SQLiteEntityStore": "__init__",
|
||||||
}
|
}
|
||||||
|
|
@ -53,8 +53,7 @@ class MemoryCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
memory.__name__
|
memory.__name__
|
||||||
for memory in self.type_to_loader_dict.values()
|
for memory in self.type_to_loader_dict.values()
|
||||||
if memory.__name__ in settings_service.settings.MEMORIES
|
if memory.__name__ in settings_service.settings.MEMORIES or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import ClassVar, Dict, List, Optional, Type
|
||||||
|
|
||||||
from langchain import output_parsers
|
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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -13,7 +13,7 @@ from langflow.utils.util import build_template_from_class, build_template_from_m
|
||||||
|
|
||||||
class OutputParserCreator(LangChainTypeCreator):
|
class OutputParserCreator(LangChainTypeCreator):
|
||||||
type_name: str = "output_parsers"
|
type_name: str = "output_parsers"
|
||||||
from_method_nodes = {
|
from_method_nodes: ClassVar[Dict] = {
|
||||||
"StructuredOutputParser": "from_response_schemas",
|
"StructuredOutputParser": "from_response_schemas",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,17 +26,14 @@ class OutputParserCreator(LangChainTypeCreator):
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
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}"
|
|
||||||
)
|
|
||||||
# if output_parser_name is not lower case it is a class
|
# if output_parser_name is not lower case it is a class
|
||||||
for output_parser_name in output_parsers.__all__
|
for output_parser_name in output_parsers.__all__
|
||||||
}
|
}
|
||||||
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_service.settings.OUTPUT_PARSERS
|
if name in settings_service.settings.OUTPUT_PARSERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ 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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
from langflow.template.frontend_node.prompts import PromptFrontendNode
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -36,8 +36,7 @@ class PromptCreator(LangChainTypeCreator):
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
name: prompt
|
name: prompt
|
||||||
for name, prompt in self.type_dict.items()
|
for name, prompt in self.type_dict.items()
|
||||||
if name in settings_service.settings.PROMPTS
|
if name in settings_service.settings.PROMPTS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from typing import Dict, List, Optional, Type
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langchain.prompts import PromptTemplate
|
from langchain.prompts import PromptTemplate
|
||||||
from pydantic import root_validator
|
from pydantic.v1 import root_validator
|
||||||
|
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
|
|
||||||
|
|
@ -42,17 +42,13 @@ class BaseCustomPrompt(PromptTemplate):
|
||||||
values["template"] = values["template"].format(**format_dict)
|
values["template"] = values["template"].format(**format_dict)
|
||||||
|
|
||||||
values["template"] = values["template"]
|
values["template"] = values["template"]
|
||||||
values["input_variables"] = extract_input_variables_from_prompt(
|
values["input_variables"] = extract_input_variables_from_prompt(values["template"])
|
||||||
values["template"]
|
|
||||||
)
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterPrompt(BaseCustomPrompt):
|
class SeriesCharacterPrompt(BaseCustomPrompt):
|
||||||
# Add a very descriptive description for the prompt generator
|
# Add a very descriptive description for the prompt generator
|
||||||
description: Optional[
|
description: Optional[str] = "A prompt that asks the AI to act like a character from a series."
|
||||||
str
|
|
||||||
] = "A prompt that asks the AI to act like a character from a series."
|
|
||||||
character: str
|
character: str
|
||||||
series: str
|
series: str
|
||||||
template: str = """I want you to act like {character} from {series}.
|
template: str = """I want you to act like {character} from {series}.
|
||||||
|
|
@ -68,6 +64,4 @@ Human: {input}
|
||||||
input_variables: List[str] = ["character", "series"]
|
input_variables: List[str] = ["character", "series"]
|
||||||
|
|
||||||
|
|
||||||
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {
|
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {"SeriesCharacterPrompt": SeriesCharacterPrompt}
|
||||||
"SeriesCharacterPrompt": SeriesCharacterPrompt
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
from typing import Any, Dict, List, Optional, Type
|
from typing import Any, ClassVar, Dict, List, Optional, Type
|
||||||
|
|
||||||
from langchain import retrievers
|
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.services.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
|
|
||||||
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
from langflow.template.frontend_node.retrievers import RetrieverFrontendNode
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -14,7 +14,10 @@ from langflow.utils.util import build_template_from_method, build_template_from_
|
||||||
class RetrieverCreator(LangChainTypeCreator):
|
class RetrieverCreator(LangChainTypeCreator):
|
||||||
type_name: str = "retrievers"
|
type_name: str = "retrievers"
|
||||||
|
|
||||||
from_method_nodes = {"MultiQueryRetriever": "from_llm", "ZepRetriever": "__init__"}
|
from_method_nodes: ClassVar[Dict] = {
|
||||||
|
"MultiQueryRetriever": "from_llm",
|
||||||
|
"ZepRetriever": "__init__",
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frontend_node_class(self) -> Type[RetrieverFrontendNode]:
|
def frontend_node_class(self) -> Type[RetrieverFrontendNode]:
|
||||||
|
|
@ -39,9 +42,7 @@ class RetrieverCreator(LangChainTypeCreator):
|
||||||
method_name=self.from_method_nodes[name],
|
method_name=self.from_method_nodes[name],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return build_template_from_class(
|
return build_template_from_class(name, type_to_cls_dict=self.type_to_loader_dict)
|
||||||
name, type_to_cls_dict=self.type_to_loader_dict
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(f"Retriever {name} not found") from exc
|
raise ValueError(f"Retriever {name} not found") from exc
|
||||||
except AttributeError as exc:
|
except AttributeError as exc:
|
||||||
|
|
@ -53,8 +54,7 @@ class RetrieverCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
retriever
|
retriever
|
||||||
for retriever in self.type_to_loader_dict.keys()
|
for retriever in self.type_to_loader_dict.keys()
|
||||||
if retriever in settings_service.settings.RETRIEVERS
|
if retriever in settings_service.settings.RETRIEVERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Optional, Tuple, Union
|
||||||
from langflow.graph import Graph
|
from uuid import UUID
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from langflow.graph import Graph
|
||||||
|
|
||||||
def build_sorted_vertices(data_graph) -> Tuple[Graph, Dict]:
|
|
||||||
|
async def build_sorted_vertices(data_graph, user_id: Optional[Union[str, UUID]] = None) -> Tuple[Graph, Dict]:
|
||||||
"""
|
"""
|
||||||
Build langchain object from data_graph.
|
Build langchain object from data_graph.
|
||||||
"""
|
"""
|
||||||
|
|
@ -13,28 +16,12 @@ def build_sorted_vertices(data_graph) -> Tuple[Graph, Dict]:
|
||||||
sorted_vertices = graph.topological_sort()
|
sorted_vertices = graph.topological_sort()
|
||||||
artifacts = {}
|
artifacts = {}
|
||||||
for vertex in sorted_vertices:
|
for vertex in sorted_vertices:
|
||||||
vertex.build()
|
await vertex.build(user_id=user_id)
|
||||||
if vertex.artifacts:
|
if vertex.artifacts:
|
||||||
artifacts.update(vertex.artifacts)
|
artifacts.update(vertex.artifacts)
|
||||||
return graph, artifacts
|
return graph, artifacts
|
||||||
|
|
||||||
|
|
||||||
def build_langchain_object(data_graph):
|
|
||||||
"""
|
|
||||||
Build langchain object from data_graph.
|
|
||||||
"""
|
|
||||||
|
|
||||||
logger.debug("Building langchain object")
|
|
||||||
nodes = data_graph["nodes"]
|
|
||||||
# Add input variables
|
|
||||||
# nodes = payload.extract_input_variables(nodes)
|
|
||||||
# Nodes, edges and root node
|
|
||||||
edges = data_graph["edges"]
|
|
||||||
graph = Graph(nodes, edges)
|
|
||||||
|
|
||||||
return graph.build()
|
|
||||||
|
|
||||||
|
|
||||||
def get_memory_key(langchain_object):
|
def get_memory_key(langchain_object):
|
||||||
"""
|
"""
|
||||||
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.
|
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
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.getters import get_settings_service
|
from langflow.services.deps import get_settings_service
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -35,8 +35,7 @@ class TextSplitterCreator(LangChainTypeCreator):
|
||||||
return [
|
return [
|
||||||
textsplitter.__name__
|
textsplitter.__name__
|
||||||
for textsplitter in self.type_to_loader_dict.values()
|
for textsplitter in self.type_to_loader_dict.values()
|
||||||
if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS
|
if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS or settings_service.settings.DEV
|
||||||
or settings_service.settings.DEV
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
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