Merge branch 'release' into langfuse_integration

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-08-30 09:53:21 -03:00
commit 2fe8e29546
71 changed files with 1279 additions and 2105 deletions

View file

@ -1,32 +1,33 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/universal
{
"name": "LangChain Demo Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:3.10",
"features": {
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {},
"ghcr.io/devcontainers/features/node": {}
},
"customizations": {
"vscode": {
"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"eamodio.gitlens"
]
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pipx install 'langflow>=0.0.33' && langflow --host 0.0.0.0"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
"name": "Langflow Demo Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:3.10",
"features": {
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {},
"ghcr.io/devcontainers/features/node": {}
},
"customizations": {
"vscode": {
"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"eamodio.gitlens",
"GitHub.vscode-pull-request-github"
]
}
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pipx install 'langflow>=0.0.33' && langflow --host 0.0.0.0"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View file

@ -1,35 +1,41 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/universal
{
"name": "LangChain Dev Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
"features": {
"ghcr.io/devcontainers/features/aws-cli:1": {},
"ghcr.io/devcontainers/features/docker-in-docker": {}
},
"customizations": {
"vscode": {"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"sourcery.sourcery",
"eamodio.gitlens"
]}
},
"name": "Langflow Dev Container",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:1-3.10-bullseye",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/node": {},
"ghcr.io/devcontainers-contrib/features/poetry": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "poetry install"
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "make install_frontend && make install_backend",
// Configure tool-specific properties.
// "customizations": {},
"containerEnv": {
"POETRY_VIRTUALENVS_IN_PROJECT": "true"
},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"actboy168.tasks",
"GitHub.copilot",
"ms-python.python",
"sourcery.sourcery",
"eamodio.gitlens",
"ms-vscode.makefile-tools",
"GitHub.vscode-pull-request-github"
]
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View file

@ -27,7 +27,8 @@ format:
cd src/frontend && npm run format
lint:
poetry run mypy .
# skip .venv folder
poetry run mypy --exclude .venv .
poetry run black . --check
poetry run ruff . --fix

View file

@ -1,11 +1,13 @@
import Admonition from '@theme/Admonition';
import Admonition from "@theme/Admonition";
# Text Splitters
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
<p>
We appreciate your understanding as we polish our documentation it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
</p>
<p>
We appreciate your understanding as we polish our documentation it may
contain some rough edges. Share your feedback or report issues to help us
improve! 🛠️📝
</p>
</Admonition>
A text splitter is a tool that divides a document or text into smaller chunks or segments. It is used to break down large texts into more manageable pieces for analysis or processing.
@ -22,13 +24,13 @@ The `CharacterTextSplitter` is used to split a long text into smaller chunks bas
- **chunk_overlap:** Determines the number of characters that overlap between consecutive chunks when splitting text. It specifies how much of the previous chunk should be included in the next chunk.
For example, if the `chunk_overlap` is set to 20 and the `chunk_size` is set to 100, the splitter will create chunks of 100 characters each, but the last 20 characters of each chunk will overlap with the first 20 characters of the next chunk. This allows for a smoother transition between chunks and ensures that no information is lost defaults to `200`.
For example, if the `chunk_overlap` is set to 20 and the `chunk_size` is set to 100, the splitter will create chunks of 100 characters each, but the last 20 characters of each chunk will overlap with the first 20 characters of the next chunk. This allows for a smoother transition between chunks and ensures that no information is lost defaults to `200`.
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
For example, if the chunk_size is set to 100, the splitter will create chunks of 100 characters each. If the text is longer than 100 characters, it will be divided into multiple chunks of equal size, except for the last chunk, which may be smaller if there are remaining characters defaults to `1000`.
For example, if the chunk_size is set to 100, the splitter will create chunks of 100 characters each. If the text is longer than 100 characters, it will be divided into multiple chunks of equal size, except for the last chunk, which may be smaller if there are remaining characters defaults to `1000`.
- **separator:** Specifies the character that will be used to split the text into chunks defaults to `.`
- **separator:** Specifies the character that will be used to split the text into chunks defaults to `.`
---
@ -44,6 +46,18 @@ The `RecursiveCharacterTextSplitter` splits the text by trying to keep paragra
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
- **separator_type:** The parameter allows the user to split the code with multiple language support. It supports various languages such as Text, Ruby, Python, Solidity, Java, and more. Defaults to `Text`.
- **separators:** The `separators` in RecursiveCharacterTextSplitter are the characters used to split the text into chunks. The text splitter tries to create chunks based on splitting on the first character in the list of `separators`. If any chunks are too large, it moves on to the next character in the list and continues splitting. Defaults to ["\n\n", "\n", " ", ""].
- **separators:** The `separators` in RecursiveCharacterTextSplitter are the characters used to split the text into chunks. The text splitter tries to create chunks based on splitting on the first character in the list of `separators`. If any chunks are too large, it moves on to the next character in the list and continues splitting. Defaults to `.`
### LanguageRecursiveTextSplitter
The `LanguageRecursiveTextSplitter` is a text splitter that splits the text into smaller chunks based on the (programming) language of the text.
**Params**
- **Documents:** Input documents to split.
- **chunk_overlap:** Determines the number of characters that overlap between consecutive chunks when splitting text. It specifies how much of the previous chunk should be included in the next chunk.
- **chunk_size:** Determines the maximum number of characters in each chunk when splitting a text. It specifies the size or length of each chunk.
- **separator_type:** The parameter allows the user to split the code with multiple language support. It supports various languages such as Ruby, Python, Solidity, Java, and more. Defaults to `Python`.

View file

@ -1,10 +1,76 @@
import Admonition from '@theme/Admonition';
import Admonition from "@theme/Admonition";
# Utilities
<Admonition type="caution" icon="🚧" title="ZONE UNDER CONSTRUCTION">
<p>
We appreciate your understanding as we polish our documentation it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
</p>
<p>
We appreciate your understanding as we polish our documentation it may
contain some rough edges. Share your feedback or report issues to help us
improve! 🛠️📝
</p>
</Admonition>
Utilities are a set of actions that can be used to perform common tasks in a flow. They are available in the **Utilities** section in the sidebar.
---
### GET Request
Make a GET request to the given URL.
**Params**
- **URL:** The URL to make the request to. There can be more than one URL, in which case the request will be made to each URL in order.
- **Headers:** A dictionary of headers to send with the request.
**Output**
- **List of Documents:** A list of Documents containing the JSON response from each request.
---
### POST Request
Make a POST request to the given URL.
**Params**
- **URL:** The URL to make the request to.
- **Headers:** A dictionary of headers to send with the request.
- **Document:** The Document containing a JSON object to send with the request.
**Output**
- **Document:** The JSON response from the request as a Document.
---
### Update Request
Make a PATCH or PUT request to the given URL.
**Params**
- **URL:** The URL to make the request to.
- **Headers:** A dictionary of headers to send with the request.
- **Document:** The Document containing a JSON object to send with the request.
- **Method:** The HTTP method to use for the request. Can be either `PATCH` or `PUT`.
**Output**
- **Document:** The JSON response from the request as a Document.
---
### JSON Document Builder
Build a Document containing a JSON object using a key and another Document page content.
**Params**
- **Key:** The key to use for the JSON object.
- **Document:** The Document page to use for the JSON object.
**Output**
- **List of Documents:** A list containing the Document with the JSON object.

71
docs/package-lock.json generated
View file

@ -16,7 +16,7 @@
"@docusaurus/theme-classic": "^2.4.1",
"@docusaurus/theme-search-algolia": "^2.4.1",
"@mdx-js/react": "^2.3.0",
"@mendable/search": "^0.0.114",
"@mendable/search": "^0.0.154",
"@pbe/react-yandex-maps": "^1.2.4",
"@prismicio/client": "^7.0.1",
"@uiball/loaders": "^1.2.6",
@ -3250,10 +3250,11 @@
}
},
"node_modules/@mendable/search": {
"version": "0.0.114",
"resolved": "https://registry.npmjs.org/@mendable/search/-/search-0.0.114.tgz",
"integrity": "sha512-0uR+zxONuu/16bpLli49Jocr5fee1WIjs06KzU1AnHsR+fdFBmfrlpgTDWctgGuXPzS5Dorlw4VMlR5dPW5qVQ==",
"version": "0.0.154",
"resolved": "https://registry.npmjs.org/@mendable/search/-/search-0.0.154.tgz",
"integrity": "sha512-adNwXlIaMXVMCkPU2uUdghfn05Dmxb0BnE95SRLQJ6evHajsNFQdRl5Ltj3WijG+qo4ozTIJcPOBYrDPKMTPVw==",
"dependencies": {
"html-react-parser": "^4.2.0",
"posthog-js": "^1.45.1"
},
"peerDependencies": {
@ -9351,6 +9352,33 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/html-dom-parser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-4.0.0.tgz",
"integrity": "sha512-TUa3wIwi80f5NF8CVWzkopBVqVAtlawUzJoLwVLHns0XSJGynss4jiY0mTWpiDOsuyw+afP+ujjMgRh9CoZcXw==",
"dependencies": {
"domhandler": "5.0.3",
"htmlparser2": "9.0.0"
}
},
"node_modules/html-dom-parser/node_modules/htmlparser2": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.0.0.tgz",
"integrity": "sha512-uxbSI98wmFT/G4P2zXx4OVx04qWUmyFPrD2/CNepa2Zo3GPNaCaaxElDgwUrwYWkK1nr9fft0Ya8dws8coDLLQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"entities": "^4.5.0"
}
},
"node_modules/html-entities": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz",
@ -9394,6 +9422,20 @@
"node": ">= 12"
}
},
"node_modules/html-react-parser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.1.tgz",
"integrity": "sha512-Dxzdowj5Zu/+7mr8X8PzCFbPXGuwCwGB2u4cB6oxZGES9inw85qlvnlfPD75VGKUGjcgsXs+9Dpj+THWNQyOBw==",
"dependencies": {
"domhandler": "5.0.3",
"html-dom-parser": "4.0.0",
"react-property": "2.0.0",
"style-to-js": "1.1.3"
},
"peerDependencies": {
"react": "0.14 || 15 || 16 || 17 || 18"
}
},
"node_modules/html-tags": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
@ -15324,6 +15366,11 @@
"react": ">=16.6.0"
}
},
"node_modules/react-property": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
"integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw=="
},
"node_modules/react-router": {
"version": "5.3.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz",
@ -17510,6 +17557,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-to-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.3.tgz",
"integrity": "sha512-zKI5gN/zb7LS/Vm0eUwjmjrXWw8IMtyA8aPBJZdYiQTXj4+wQ3IucOLIOnF7zCHxvW8UhIGh/uZh/t9zEHXNTQ==",
"dependencies": {
"style-to-object": "0.4.1"
}
},
"node_modules/style-to-js/node_modules/style-to-object": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz",
"integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==",
"dependencies": {
"inline-style-parser": "0.1.1"
}
},
"node_modules/style-to-object": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",

View file

@ -22,7 +22,7 @@
"@docusaurus/theme-classic": "^2.4.1",
"@docusaurus/theme-search-algolia": "^2.4.1",
"@mdx-js/react": "^2.3.0",
"@mendable/search": "^0.0.114",
"@mendable/search": "^0.0.154",
"@pbe/react-yandex-maps": "^1.2.4",
"@prismicio/client": "^7.0.1",
"@uiball/loaders": "^1.2.6",
@ -69,4 +69,4 @@
"engines": {
"node": ">=16.14"
}
}
}

View file

@ -42,6 +42,7 @@ module.exports = {
"components/text-splitters",
"components/toolkits",
"components/tools",
"components/utilities",
"components/vector-stores",
"components/wrappers",
],

View file

@ -37,7 +37,7 @@ export default function FooterWrapper(props) {
const mendableFloatingButton = React.createElement(MendableFloatingButton, {
floatingButtonStyle: { color: "#000000", backgroundColor: "#f6f6f6" },
anon_key: customFields.mendableAnonKey, // Mendable Search Public ANON key, ok to be public
anon_key: 'b7f52734-297c-41dc-8737-edbd13196394', // Mendable Search Public ANON key, ok to be public
showSimpleSearch: true,
icon: icon,
});

1551
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
{
"devDependencies": {
"@svgr/cli": "^8.0.1"
}
}

255
poetry.lock generated
View file

@ -206,17 +206,6 @@ files = [
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
]
[[package]]
name = "argilla"
version = "0.0.1"
description = ""
optional = false
python-versions = "*"
files = [
{file = "argilla-0.0.1-py3-none-any.whl", hash = "sha256:8bdc3c505bcfb47ba4b91f5658034eae53bf7d4f9317980397605c0c55817396"},
{file = "argilla-0.0.1.tar.gz", hash = "sha256:5017854754e89f573b31af25b25b803f51cea9ca1fa0bcf00505dee1f45cf7c9"},
]
[[package]]
name = "asgiref"
version = "3.7.2"
@ -1235,16 +1224,19 @@ gmpy = ["gmpy"]
gmpy2 = ["gmpy2"]
[[package]]
name = "et-xmlfile"
version = "1.1.0"
description = "An implementation of lxml.xmlfile for the standard library"
name = "emoji"
version = "2.8.0"
description = "Emoji for Python"
optional = false
python-versions = ">=3.6"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"},
{file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"},
{file = "emoji-2.8.0-py2.py3-none-any.whl", hash = "sha256:a8468fd836b7ecb6d1eac054c9a591701ce0ccd6c6f7779ad71b66f76664df90"},
{file = "emoji-2.8.0.tar.gz", hash = "sha256:8d8b5dec3c507444b58890e598fc895fcec022b3f5acb49497c6ccc5208b8b00"},
]
[package.extras]
dev = ["coverage", "coveralls", "pytest"]
[[package]]
name = "exceptiongroup"
version = "1.1.3"
@ -3160,24 +3152,6 @@ docs = ["sphinx (>=1.6.0)", "sphinx-bootstrap-theme"]
flake8 = ["flake8"]
tests = ["psutil", "pytest (!=3.3.0)", "pytest-cov"]
[[package]]
name = "markdown"
version = "3.4.4"
description = "Python implementation of John Gruber's Markdown."
optional = false
python-versions = ">=3.7"
files = [
{file = "Markdown-3.4.4-py3-none-any.whl", hash = "sha256:a4c1b65c0957b4bd9e7d86ddc7b3c9868fb9670660f6f99f6d1bca8954d5a941"},
{file = "Markdown-3.4.4.tar.gz", hash = "sha256:225c6123522495d4119a90b3a3ba31a1e87a70369e03f14799ea9c0d7183a3d6"},
]
[package.dependencies]
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.0)", "mkdocs-nature (>=0.4)"]
testing = ["coverage", "pyyaml"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
@ -3327,6 +3301,21 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "metal-sdk"
version = "2.0.2"
description = "SDK for getmetal.io"
optional = false
python-versions = ">=3.7"
files = [
{file = "metal_sdk-2.0.2-py3-none-any.whl", hash = "sha256:a5a95fb3796979085965667ffc41fd9eeddc87b60671619671a0eaebd0bec4a1"},
{file = "metal_sdk-2.0.2.tar.gz", hash = "sha256:e5e35d230f00b1b838cfcffbcb7042f450b23b59448f37bdd385761871a1490d"},
]
[package.dependencies]
httpx = "*"
typing-extensions = "*"
[[package]]
name = "metaphor-python"
version = "0.1.16"
@ -3380,23 +3369,6 @@ docs = ["sphinx"]
gmpy = ["gmpy2 (>=2.1.0a4)"]
tests = ["pytest (>=4.6)"]
[[package]]
name = "msg-parser"
version = "1.2.0"
description = "This module enables reading, parsing and converting Microsoft Outlook MSG E-Mail files."
optional = false
python-versions = ">=3.4"
files = [
{file = "msg_parser-1.2.0-py2.py3-none-any.whl", hash = "sha256:d47a2f0b2a359cb189fad83cc991b63ea781ecc70d91410324273fbf93e95375"},
{file = "msg_parser-1.2.0.tar.gz", hash = "sha256:0de858d4fcebb6c8f6f028da83a17a20fe01cdce67c490779cf43b3b0162aa66"},
]
[package.dependencies]
olefile = ">=0.46"
[package.extras]
rtf = ["compressed-rtf (>=1.0.5)"]
[[package]]
name = "multidict"
version = "6.0.4"
@ -3695,16 +3667,6 @@ files = [
{file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"},
]
[[package]]
name = "olefile"
version = "0.46"
description = "Python package to parse, read and write Microsoft OLE2 files (Structured Storage or Compound Document, Microsoft Office)"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"},
]
[[package]]
name = "onnxruntime"
version = "1.15.1"
@ -3782,20 +3744,6 @@ files = [
[package.dependencies]
pydantic = ">=1.8.2"
[[package]]
name = "openpyxl"
version = "3.1.2"
description = "A Python library to read/write Excel 2010 xlsx/xlsm files"
optional = false
python-versions = ">=3.6"
files = [
{file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"},
{file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"},
]
[package.dependencies]
et-xmlfile = "*"
[[package]]
name = "opentelemetry-api"
version = "1.19.0"
@ -4254,40 +4202,6 @@ files = [
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "pdf2image"
version = "1.16.3"
description = "A wrapper around the pdftoppm and pdftocairo command line tools to convert PDF to a PIL Image list."
optional = false
python-versions = "*"
files = [
{file = "pdf2image-1.16.3-py3-none-any.whl", hash = "sha256:b6154164af3677211c22cbb38b2bd778b43aca02758e962fe1e231f6d3b0e380"},
{file = "pdf2image-1.16.3.tar.gz", hash = "sha256:74208810c2cef4d9e347769b8e62a52303982ddb4f2dfd744c7ab4b940ae287e"},
]
[package.dependencies]
pillow = "*"
[[package]]
name = "pdfminer-six"
version = "20221105"
description = "PDF parser and analyzer"
optional = false
python-versions = ">=3.6"
files = [
{file = "pdfminer.six-20221105-py3-none-any.whl", hash = "sha256:1eaddd712d5b2732f8ac8486824533514f8ba12a0787b3d5fe1e686cd826532d"},
{file = "pdfminer.six-20221105.tar.gz", hash = "sha256:8448ab7b939d18b64820478ecac5394f482d7a79f5f7eaa7703c6c959c175e1d"},
]
[package.dependencies]
charset-normalizer = ">=2.0.0"
cryptography = ">=36.0.0"
[package.extras]
dev = ["black", "mypy (==0.931)", "nox", "pytest"]
docs = ["sphinx", "sphinx-argparse"]
image = ["Pillow"]
[[package]]
name = "pexpect"
version = "4.8.0"
@ -5088,17 +5002,6 @@ ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3
snappy = ["python-snappy"]
zstd = ["zstandard"]
[[package]]
name = "pypandoc"
version = "1.11"
description = "Thin wrapper for pandoc."
optional = false
python-versions = ">=3.6"
files = [
{file = "pypandoc-1.11-py3-none-any.whl", hash = "sha256:b260596934e9cfc6513056110a7c8600171d414f90558bf4407e68b209be8007"},
{file = "pypandoc-1.11.tar.gz", hash = "sha256:7f6d68db0e57e0f6961bec2190897118c4d305fc2d31c22cd16037f22ee084a5"},
]
[[package]]
name = "pyparsing"
version = "3.1.1"
@ -5212,19 +5115,6 @@ files = [
[package.dependencies]
six = ">=1.5"
[[package]]
name = "python-docx"
version = "0.8.11"
description = "Create and update Microsoft Word .docx files."
optional = false
python-versions = "*"
files = [
{file = "python-docx-0.8.11.tar.gz", hash = "sha256:1105d233a0956dd8dd1e710d20b159e2d72ac3c301041b95f4d4ceb3e0ebebc4"},
]
[package.dependencies]
lxml = ">=2.3.2"
[[package]]
name = "python-dotenv"
version = "1.0.0"
@ -5304,21 +5194,6 @@ files = [
[package.extras]
dev = ["atomicwrites (==1.2.1)", "attrs (==19.2.0)", "coverage (==6.5.0)", "hatch", "invoke (==1.7.3)", "more-itertools (==4.3.0)", "pbr (==4.3.0)", "pluggy (==1.0.0)", "py (==1.11.0)", "pytest (==7.2.0)", "pytest-cov (==4.0.0)", "pytest-timeout (==2.1.0)", "pyyaml (==5.1)"]
[[package]]
name = "python-pptx"
version = "0.6.21"
description = "Generate and manipulate Open XML PowerPoint (.pptx) files"
optional = false
python-versions = "*"
files = [
{file = "python-pptx-0.6.21.tar.gz", hash = "sha256:7798a2aaf89563565b3c7120c0acfe9aff775db0db3580544e3bf4840c2e378f"},
]
[package.dependencies]
lxml = ">=3.1.0"
Pillow = ">=3.3.2"
XlsxWriter = ">=0.5.7"
[[package]]
name = "python-semantic-release"
version = "7.33.2"
@ -6995,50 +6870,67 @@ test = ["coverage", "pytest", "pytest-cov"]
[[package]]
name = "unstructured"
version = "0.7.12"
version = "0.10.9"
description = "A library that prepares raw documents for downstream ML tasks."
optional = false
python-versions = ">=3.7.0"
files = [
{file = "unstructured-0.7.12-py3-none-any.whl", hash = "sha256:6dec4f23574e213f30bccb680a4fb84c95617092ce4abf5d8955cc71af402fef"},
{file = "unstructured-0.7.12.tar.gz", hash = "sha256:3dcddea34f52e1070f38fd10063b3b0f64bc4cbe5b778d6b86b5d33262d625cd"},
{file = "unstructured-0.10.9-py3-none-any.whl", hash = "sha256:182062983cf5ade923e871be52154829d92e5b72dfd7575847618fb44f7debd6"},
{file = "unstructured-0.10.9.tar.gz", hash = "sha256:fd62f84abf12c85ef8b81567a4fcb841b7450f010454b69754bfc5b10b68d869"},
]
[package.dependencies]
argilla = "*"
beautifulsoup4 = "*"
chardet = "*"
emoji = "*"
filetype = "*"
lxml = "*"
markdown = "*"
msg-parser = "*"
nltk = "*"
openpyxl = "*"
pandas = "*"
pdf2image = "*"
"pdfminer.six" = "*"
pillow = "*"
pypandoc = "*"
python-docx = "*"
python-magic = "*"
python-pptx = "*"
requests = "*"
tabulate = "*"
xlrd = "*"
[package.extras]
airtable = ["pyairtable"]
all-docs = ["Pillow (<10)", "ebooklib", "markdown", "msg-parser", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pypandoc", "python-docx", "python-pptx", "unstructured-inference", "xlrd"]
azure = ["adlfs", "fsspec"]
biomed = ["bs4"]
box = ["boxfs", "fsspec"]
confluence = ["atlassian-python-api"]
csv = ["pandas"]
delta-table = ["deltalake", "fsspec"]
discord = ["discord-py"]
doc = ["python-docx"]
docx = ["python-docx"]
dropbox = ["dropboxdrivefs", "fsspec"]
gcs = ["fsspec", "gcsfs"]
github = ["pygithub (==1.58.2)"]
elasticsearch = ["elasticsearch", "jq"]
epub = ["ebooklib"]
gcs = ["bs4", "fsspec", "gcsfs"]
github = ["pygithub (>1.58.0)"]
gitlab = ["python-gitlab"]
google-drive = ["google-api-python-client"]
huggingface = ["langdetect", "sacremoses", "sentencepiece", "torch", "transformers"]
local-inference = ["unstructured-inference (==0.5.4)"]
image = ["Pillow (<10)", "pdf2image", "pdfminer.six", "unstructured-inference"]
local-inference = ["Pillow (<10)", "ebooklib", "markdown", "msg-parser", "openpyxl", "pandas", "pdf2image", "pdfminer.six", "pypandoc", "python-docx", "python-pptx", "unstructured-inference", "xlrd"]
md = ["markdown"]
msg = ["msg-parser"]
notion = ["htmlBuilder", "notion-client"]
odt = ["pypandoc", "python-docx"]
onedrive = ["Office365-REST-Python-Client (<2.4.3)", "bs4", "msal"]
org = ["pypandoc"]
outlook = ["Office365-REST-Python-Client (<2.4.3)", "msal"]
pdf = ["Pillow (<10)", "pdf2image", "pdfminer.six", "unstructured-inference"]
ppt = ["python-pptx"]
pptx = ["python-pptx"]
reddit = ["praw"]
rst = ["pypandoc"]
rtf = ["pypandoc"]
s3 = ["fsspec", "s3fs"]
sharepoint = ["Office365-REST-Python-Client (<2.4.3)", "msal"]
slack = ["slack-sdk"]
tsv = ["pandas"]
wikipedia = ["wikipedia"]
xlsx = ["openpyxl", "pandas", "xlrd"]
[[package]]
name = "uritemplate"
@ -7443,33 +7335,6 @@ files = [
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
]
[[package]]
name = "xlrd"
version = "2.0.1"
description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [
{file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"},
{file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"},
]
[package.extras]
build = ["twine", "wheel"]
docs = ["sphinx"]
test = ["pytest", "pytest-cov"]
[[package]]
name = "xlsxwriter"
version = "3.1.2"
description = "A Python module for creating Excel XLSX files."
optional = false
python-versions = ">=3.6"
files = [
{file = "XlsxWriter-3.1.2-py3-none-any.whl", hash = "sha256:331508ff39d610ecdaf979e458840bc1eab6e6a02cfd5d08f044f0f73636236f"},
{file = "XlsxWriter-3.1.2.tar.gz", hash = "sha256:78751099a770273f1c98b8d6643351f68f98ae8e6acf9d09d37dc6798f8cd3de"},
]
[[package]]
name = "yarl"
version = "1.9.2"
@ -7638,4 +7503,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.11"
content-hash = "3517d8ba2744a059d3f107a826392c25ed57b5279ccf45ffdb8e188f959b256a"
content-hash = "2390a7da4e661d287aa15bfe00ba84789301af5cca5dbc584f0093461670364e"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow"
version = "0.4.9"
version = "0.4.17"
description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"]
maintainers = [
@ -32,33 +32,33 @@ beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0"
typer = "^0.9.0"
gunicorn = "^21.1.0"
gunicorn = "^21.2.0"
langchain = "^0.0.256"
openai = "^0.27.8"
pandas = "^2.0.0"
chromadb = "^0.3.21"
chromadb = "^0.3.0"
huggingface-hub = { version = "^0.16.0", extras = ["inference"] }
rich = "^13.4.2"
rich = "^13.5.0"
llama-cpp-python = { version = "~0.1.0", optional = true }
networkx = "^3.1"
unstructured = "^0.7.0"
pypdf = "^3.11.0"
unstructured = "^0.10.0"
pypdf = "^3.15.0"
lxml = "^4.9.2"
pysrt = "^1.1.2"
fake-useragent = "^1.1.3"
fake-useragent = "^1.2.1"
docstring-parser = "^0.15"
psycopg2-binary = "^2.9.6"
pyarrow = "^12.0.0"
tiktoken = "~0.4.0"
wikipedia = "^1.4.0"
langchain-serve = { version = ">0.0.51", optional = true }
qdrant-client = "^1.3.0"
qdrant-client = "^1.4.0"
websockets = "^10.3"
weaviate-client = "^3.21.0"
weaviate-client = "^3.23.0"
jina = "3.15.2"
sentence-transformers = { version = "^2.2.2", optional = true }
ctransformers = { version = "^0.2.10", optional = true }
cohere = "^4.11.0"
cohere = "^4.21.0"
python-multipart = "^0.0.6"
sqlmodel = "^0.0.8"
faiss-cpu = "^1.7.4"
@ -80,6 +80,8 @@ fastavro = "^1.8.0"
langchain-experimental = "^0.0.8"
metaphor-python = "^0.1.11"
langfuse = "^1.0.13"
pillow = "^10.0.0"
metal-sdk = "^2.0.2"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"

View file

@ -104,14 +104,9 @@ async def stream_build(flow_id: str):
return
logger.debug("Building langchain object")
try:
# Some error could happen when building the graph
graph = Graph.from_payload(graph_data)
except Exception as exc:
logger.exception(exc)
error_message = str(exc)
yield str(StreamData(event="error", data={"error": error_message}))
return
# Some error could happen when building the graph
graph = Graph.from_payload(graph_data)
number_of_nodes = len(graph.nodes)
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
@ -126,7 +121,9 @@ async def stream_build(flow_id: str):
params = vertex._built_object_repr()
valid = True
logger.debug(f"Building node {str(vertex.vertex_type)}")
logger.debug(f"Output: {params}")
logger.debug(
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
)
if vertex.artifacts:
# The artifacts will be prompt variables
# passed to build_input_keys_response

View file

@ -56,12 +56,9 @@ def get_all():
logger.info(f"Loading {len(custom_component_dicts)} category(ies)")
for custom_component_dict in custom_component_dicts:
# custom_component_dict is a dict of dicts
category = list(custom_component_dict.keys())[0]
logger.info(
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
logger.debug(
{key: len(value) for key, value in custom_component_dict.items()}
)
logger.debug(custom_component_dict)
custom_components_from_file = merge_nested_dicts_with_renaming(
custom_components_from_file, custom_component_dict
)

View file

@ -1,5 +1,6 @@
from typing import List
from uuid import UUID
from fastapi.encoders import jsonable_encoder
from langflow.settings import settings
from langflow.api.utils import remove_api_keys
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
@ -11,12 +12,11 @@ from langflow.database.models.flow import (
FlowUpdate,
)
from langflow.database.base import get_session
import orjson
from sqlmodel import Session, select
from fastapi import APIRouter, Depends, HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi import File, UploadFile
import json
# build router
router = APIRouter(prefix="/flows", tags=["Flows"])
@ -105,7 +105,7 @@ async def upload_file(
):
"""Upload flows from a file."""
contents = await file.read()
data = json.loads(contents)
data = orjson.loads(contents)
if "flows" in data:
flow_list = FlowListCreate(**data)
else:

View file

@ -1,9 +1,9 @@
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from langflow.database.models.base import orjson_dumps
from langflow.database.models.flow import FlowCreate, FlowRead
from pydantic import BaseModel, Field, validator
import json
class BuildStatus(Enum):
@ -115,7 +115,9 @@ class StreamData(BaseModel):
data: dict
def __str__(self) -> str:
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n"
return (
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
)
class CustomComponentCode(BaseModel):

View file

@ -2,13 +2,13 @@ import base64
import contextlib
import functools
import hashlib
import json
import os
import tempfile
from collections import OrderedDict
from pathlib import Path
from typing import Any, Dict
from appdirs import user_cache_dir
from langflow.database.models.base import orjson_dumps
CACHE: Dict[str, Any] = {}
@ -76,7 +76,8 @@ def clear_old_cache_files(max_cache_size: int = 3):
def compute_dict_hash(graph_data):
graph_data = filter_json(graph_data)
cleaned_graph_json = json.dumps(graph_data, sort_keys=True)
cleaned_graph_json = orjson_dumps(graph_data, sort_keys=True)
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()

View file

@ -10,10 +10,10 @@ from langflow.utils.logger import logger
import asyncio
import json
from typing import Any, Dict, List
from langflow.cache.flow import InMemoryCache
import orjson
class ChatHistory(Subject):
@ -193,8 +193,8 @@ class ChatManager:
while True:
json_payload = await websocket.receive_json()
try:
payload = json.loads(json_payload)
except TypeError:
payload = orjson.loads(json_payload)
except Exception:
payload = json_payload
if "clear_history" in payload:
self.chat_history.history[client_id] = []

View file

@ -42,8 +42,8 @@ class ConversationalAgent(CustomComponent):
self,
model_name: str,
openai_api_key: str,
openai_api_base: str,
tools: Tool,
openai_api_base: Optional[str] = None,
memory: Optional[BaseMemory] = None,
system_message: Optional[SystemMessagePromptTemplate] = None,
max_token_limit: int = 2000,

View file

@ -0,0 +1,42 @@
from typing import Optional
from langflow import CustomComponent
from langchain.llms import HuggingFaceEndpoint
from langchain.llms.base import BaseLLM
class HuggingFaceEndpointsComponent(CustomComponent):
display_name: str = "Hugging Face Inference API"
description: str = "LLM model from Hugging Face Inference API."
def build_config(self):
return {
"endpoint_url": {"display_name": "Endpoint URL", "password": True},
"task": {
"display_name": "Task",
"type": "select",
"options": ["text2text-generation", "text-generation", "summarization"],
},
"huggingfacehub_api_token": {"display_name": "API token", "password": True},
"model_kwargs": {
"display_name": "Model Keyword Arguments",
"field_type": "code",
},
"code": {"show": False},
}
def build(
self,
endpoint_url: str,
task="text2text-generation",
huggingfacehub_api_token: Optional[str] = None,
model_kwargs: Optional[dict] = None,
) -> BaseLLM:
try:
output = HuggingFaceEndpoint(
endpoint_url=endpoint_url,
task=task,
huggingfacehub_api_token=huggingfacehub_api_token,
)
except Exception as e:
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
return output

View file

@ -0,0 +1,28 @@
from typing import Optional
from langflow import CustomComponent
from langchain.retrievers import MetalRetriever
from langchain.schema import BaseRetriever
from metal_sdk.metal import Metal # type: ignore
class MetalRetrieverComponent(CustomComponent):
display_name: str = "Metal Retriever"
description: str = "Retriever that uses the Metal API."
def build_config(self):
return {
"api_key": {"display_name": "API Key", "password": True},
"client_id": {"display_name": "Client ID", "password": True},
"index_id": {"display_name": "Index ID"},
"params": {"display_name": "Parameters", "field_type": "code"},
"code": {"show": False},
}
def build(
self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None
) -> BaseRetriever:
try:
metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id)
except Exception as e:
raise ValueError("Could not connect to Metal API.") from e
return MetalRetriever(client=metal, params=params or {})

View file

@ -0,0 +1,82 @@
from typing import Optional
from langflow import CustomComponent
from langchain.text_splitter import Language
from langchain.schema import Document
from langflow.utils.util import build_loader_repr_from_documents
class LanguageRecursiveTextSplitterComponent(CustomComponent):
display_name: str = "Language Recursive Text Splitter"
description: str = "Split text into chunks of a specified length based on language."
documentation: str = "https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
def build_config(self):
options = [x.value for x in Language]
return {
"documents": {
"display_name": "Documents",
"info": "The documents to split.",
},
"separator_type": {
"display_name": "Separator Type",
"info": "The type of separator to use.",
"field_type": "str",
"options": options,
"value": "Python",
},
"separators": {
"display_name": "Separators",
"info": "The characters to split on.",
"is_list": True,
},
"chunk_size": {
"display_name": "Chunk Size",
"info": "The maximum length of each chunk.",
"field_type": "int",
"value": 1000,
},
"chunk_overlap": {
"display_name": "Chunk Overlap",
"info": "The amount of overlap between chunks.",
"field_type": "int",
"value": 200,
},
"code": {"show": False},
}
def build(
self,
documents: list[Document],
chunk_size: Optional[int] = 1000,
chunk_overlap: Optional[int] = 200,
separator_type: Optional[str] = "Python",
) -> list[Document]:
"""
Split text into chunks of a specified length.
Args:
separators (list[str]): The characters to split on.
chunk_size (int): The maximum length of each chunk.
chunk_overlap (int): The amount of overlap between chunks.
length_function (function): The function to use to calculate the length of the text.
Returns:
list[str]: The chunks of text.
"""
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Make sure chunk_size and chunk_overlap are ints
if isinstance(chunk_size, str):
chunk_size = int(chunk_size)
if isinstance(chunk_overlap, str):
chunk_overlap = int(chunk_overlap)
splitter = RecursiveCharacterTextSplitter.from_language(
language=Language(separator_type),
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
docs = splitter.split_documents(documents)
self.repr_value = build_loader_repr_from_documents(docs)
return docs

View file

@ -0,0 +1,79 @@
from typing import Optional
from langflow import CustomComponent
from langchain.schema import Document
class RecursiveCharacterTextSplitterComponent(CustomComponent):
display_name: str = "Recursive Character Text Splitter"
description: str = "Split text into chunks of a specified length."
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
def build_config(self):
return {
"documents": {
"display_name": "Documents",
"info": "The documents to split.",
},
"separators": {
"display_name": "Separators",
"info": 'The characters to split on.\nIf left empty defaults to ["\\n\\n", "\\n", " ", ""].',
"is_list": True,
},
"chunk_size": {
"display_name": "Chunk Size",
"info": "The maximum length of each chunk.",
"field_type": "int",
"value": 1000,
},
"chunk_overlap": {
"display_name": "Chunk Overlap",
"info": "The amount of overlap between chunks.",
"field_type": "int",
"value": 200,
},
"code": {"show": False},
}
def build(
self,
documents: list[Document],
separators: Optional[list[str]] = None,
chunk_size: Optional[int] = 1000,
chunk_overlap: Optional[int] = 200,
) -> list[Document]:
"""
Split text into chunks of a specified length.
Args:
separators (list[str]): The characters to split on.
chunk_size (int): The maximum length of each chunk.
chunk_overlap (int): The amount of overlap between chunks.
length_function (function): The function to use to calculate the length of the text.
Returns:
list[str]: The chunks of text.
"""
from langchain.text_splitter import RecursiveCharacterTextSplitter
if separators == "":
separators = None
elif separators:
# check if the separators list has escaped characters
# if there are escaped characters, unescape them
separators = [x.encode().decode("unicode-escape") for x in separators]
# Make sure chunk_size and chunk_overlap are ints
if isinstance(chunk_size, str):
chunk_size = int(chunk_size)
if isinstance(chunk_overlap, str):
chunk_overlap = int(chunk_overlap)
splitter = RecursiveCharacterTextSplitter(
separators=separators,
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
docs = splitter.split_documents(documents)
# self.repr_value = build_loader_repr_from_documents(docs)
self.repr_value = separators
return docs

View file

@ -0,0 +1,76 @@
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
import requests
from typing import Optional
class GetRequest(CustomComponent):
display_name: str = "GET Request"
description: str = "Make a GET request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#get-request"
beta = True
field_config = {
"url": {
"display_name": "URL",
"info": "The URL to make the request to",
"is_list": True,
},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"timeout": {
"display_name": "Timeout",
"field_type": "int",
"info": "The timeout to use for the request.",
"value": 5,
},
}
def get_document(
self, session: requests.Session, url: str, headers: Optional[dict], timeout: int
) -> Document:
try:
response = session.get(url, headers=headers, timeout=int(timeout))
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response.status_code,
},
)
except requests.Timeout:
return Document(
page_content="Request Timed Out",
metadata={"source": url, "headers": headers, "status_code": 408},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={"source": url, "headers": headers, "status_code": 500},
)
def build(
self,
url: str,
headers: Optional[dict] = None,
timeout: int = 5,
) -> list[Document]:
if headers is None:
headers = {}
urls = url if isinstance(url, list) else [url]
with requests.Session() as session:
documents = [self.get_document(session, u, headers, timeout) for u in urls]
self.repr_value = documents
return documents

View file

@ -0,0 +1,55 @@
### JSON Document Builder
# Build a Document containing a JSON object using a key and another Document page content.
# **Params**
# - **Key:** The key to use for the JSON object.
# - **Document:** The Document page to use for the JSON object.
# **Output**
# - **Document:** The Document containing the JSON object.
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
class JSONDocumentBuilder(CustomComponent):
display_name: str = "JSON Document Builder"
description: str = "Build a Document containing a JSON object using a key and another Document page content."
output_types: list[str] = ["Document"]
beta = True
documentation: str = (
"https://docs.langflow.org/components/utilities#json-document-builder"
)
field_config = {
"key": {"display_name": "Key"},
"document": {"display_name": "Document"},
}
def build(
self,
key: str,
document: Document,
) -> Document:
documents = None
if isinstance(document, list):
documents = [
Document(
page_content=orjson_dumps({key: doc.page_content}, indent_2=False)
)
for doc in document
]
elif isinstance(document, Document):
documents = Document(
page_content=orjson_dumps({key: document.page_content}, indent_2=False)
)
else:
raise TypeError(
f"Expected Document or list of Documents, got {type(document)}"
)
self.repr_value = documents
return documents

View file

@ -0,0 +1,81 @@
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
import requests
from typing import Optional
class PostRequest(CustomComponent):
display_name: str = "POST Request"
description: str = "Make a POST request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#post-request"
beta = True
field_config = {
"url": {"display_name": "URL", "info": "The URL to make the request to."},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"document": {"display_name": "Document"},
}
def post_document(
self,
session: requests.Session,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> Document:
try:
response = session.post(url, headers=headers, data=document.page_content)
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response,
},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={
"source": url,
"headers": headers,
"status_code": 500,
},
)
def build(
self,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> list[Document]:
if headers is None:
headers = {}
if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document]
elif isinstance(document, list) and all(
isinstance(doc, Document) for doc in document
):
documents = document
else:
raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session:
documents = [
self.post_document(session, doc, url, headers) for doc in documents
]
self.repr_value = documents
return documents

View file

@ -0,0 +1,94 @@
from typing import List, Optional
import requests
from langflow import CustomComponent
from langchain.schema import Document
from langflow.database.models.base import orjson_dumps
class UpdateRequest(CustomComponent):
display_name: str = "Update Request"
description: str = "Make a PATCH request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#update-request"
beta = True
field_config = {
"url": {"display_name": "URL", "info": "The URL to make the request to."},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"document": {"display_name": "Document"},
"method": {
"display_name": "Method",
"field_type": "str",
"info": "The HTTP method to use.",
"options": ["PATCH", "PUT"],
"value": "PATCH",
},
}
def update_document(
self,
session: requests.Session,
document: Document,
url: str,
headers: Optional[dict] = None,
method: str = "PATCH",
) -> Document:
try:
if method == "PATCH":
response = session.patch(
url, headers=headers, data=document.page_content
)
elif method == "PUT":
response = session.put(url, headers=headers, data=document.page_content)
else:
raise ValueError(f"Unsupported method: {method}")
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response.status_code,
},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={"source": url, "headers": headers, "status_code": 500},
)
def build(
self,
method: str,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> List[Document]:
if headers is None:
headers = {}
if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document]
elif isinstance(document, list) and all(
isinstance(doc, Document) for doc in document
):
documents = document
else:
raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session:
documents = [
self.update_document(session, doc, url, headers, method)
for doc in documents
]
self.repr_value = documents
return documents

View file

@ -169,8 +169,6 @@ prompts:
textsplitters:
CharacterTextSplitter:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
RecursiveCharacterTextSplitter:
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter"
toolkits:
OpenAPIToolkit:
documentation: ""

View file

@ -2,9 +2,20 @@ from sqlmodel import SQLModel
import orjson
def orjson_dumps(v, *, default):
# orjson.dumps returns bytes, to match standard json.dumps we need to decode
return orjson.dumps(v, default=default).decode()
def orjson_dumps(v, *, default=None, sort_keys=False, indent_2=True):
option = orjson.OPT_SORT_KEYS if sort_keys else None
if indent_2:
# orjson.dumps returns bytes, to match standard json.dumps we need to decode
# option
# To modify how data is serialized, specify option. Each option is an integer constant in orjson.
# To specify multiple options, mask them together, e.g., option=orjson.OPT_STRICT_INTEGER | orjson.OPT_NAIVE_UTC
if option is None:
option = orjson.OPT_INDENT_2
else:
option |= orjson.OPT_INDENT_2
if default is None:
return orjson.dumps(v, option=option).decode()
return orjson.dumps(v, default=default, option=option).decode()
class SQLModelSerializable(SQLModel):

View file

@ -40,7 +40,6 @@ class Edge:
if no_matched_type:
logger.debug(self.source_types)
logger.debug(self.target_reqs)
if no_matched_type:
raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
f"has no matched type"

View file

@ -3,6 +3,7 @@ from fastapi import HTTPException
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.component import Component
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.interface.custom.utils import extract_inner_type
from langflow.utils import validate
@ -19,7 +20,7 @@ class CustomComponent(Component, extra=Extra.allow):
function_entrypoint_name = "build"
function: Optional[Callable] = None
return_type_valid_list = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
repr_value: Optional[str] = ""
repr_value: Optional[Any] = ""
def __init__(self, **data):
super().__init__(**data)
@ -122,6 +123,10 @@ class CustomComponent(Component, extra=Extra.allow):
return_type = build_method["return_type"]
if not return_type:
return []
# 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"):
return_type = extract_inner_type(return_type)
# If the return type is not a Union, then we just return it as a list
if "Union" not in return_type:
return [return_type] if return_type in self.return_type_valid_list else []

View file

@ -77,7 +77,7 @@ class DirectoryReader:
]
filtered = [menu for menu in items if menu["components"]]
logger.debug(
f'Filtered components {"with errors" if with_errors else ""}: {filtered}'
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
)
return {"menu": filtered}

View file

@ -0,0 +1,10 @@
import re
def extract_inner_type(return_type: str) -> str:
"""
Extracts the inner type from a type hint that is a list.
"""
if match := re.match(r"list\[(.*)\]", return_type, re.IGNORECASE):
return match[1]
return return_type

View file

@ -1,4 +1,5 @@
import json
import orjson
from typing import Any, Callable, Dict, Sequence, Type
from langchain.agents import agent as agent_module
@ -66,7 +67,7 @@ def convert_kwargs(params):
for key in kwargs_keys:
if isinstance(params[key], str):
try:
params[key] = json.loads(params[key])
params[key] = orjson.loads(params[key])
except json.JSONDecodeError:
# if the string is not a valid json string, we will
# remove the key from the params
@ -306,7 +307,7 @@ def instantiate_documentloader(class_object: Type[BaseLoader], params: Dict):
metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str):
try:
metadata = json.loads(metadata)
metadata = orjson.loads(metadata)
except json.JSONDecodeError as exc:
raise ValueError(
"The metadata you provided is not a valid JSON string."

View file

@ -1,5 +1,7 @@
import contextlib
import json
from langflow.database.models.base import orjson_dumps
import orjson
from typing import Any, Dict, List
from langchain.agents import ZeroShotAgent
@ -95,9 +97,11 @@ def format_content(variable):
def try_to_load_json(content):
with contextlib.suppress(json.JSONDecodeError):
content = json.loads(content)
content = orjson.loads(content)
if isinstance(content, list):
content = ",".join([str(item) for item in content])
else:
content = orjson_dumps(content)
return content

View file

@ -1,4 +1,3 @@
import json
from typing import Any, Callable, Dict, Type
from langchain.vectorstores import (
Pinecone,
@ -12,6 +11,8 @@ from langchain.vectorstores import (
import os
import orjson
def docs_in_params(params: dict) -> bool:
"""Check if params has documents OR texts and one of them is not an empty list,
@ -92,7 +93,7 @@ def initialize_weaviate(class_object: Type[Weaviate], params: dict):
import weaviate # type: ignore
client_kwargs_json = params.get("client_kwargs", "{}")
client_kwargs = json.loads(client_kwargs_json)
client_kwargs = orjson.loads(client_kwargs_json)
client_params = {
"url": params.get("weaviate_url"),
}

View file

@ -5,6 +5,7 @@ from langflow.api.utils import merge_nested_dicts_with_renaming
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.utils import extract_inner_type
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.importing.utils import get_function_custom
@ -84,6 +85,8 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
def process_type(field_type: str):
if field_type.startswith("list") or field_type.startswith("List"):
return extract_inner_type(field_type)
return "prompt" if field_type == "Prompt" else field_type
@ -100,6 +103,7 @@ def add_new_custom_field(
# if it is, update the value
display_name = field_config.pop("display_name", field_name)
field_type = field_config.pop("field_type", field_type)
field_contains_list = "list" in field_type.lower()
field_type = process_type(field_type)
field_value = field_config.pop("value", field_value)
field_advanced = field_config.pop("advanced", False)
@ -110,7 +114,9 @@ def add_new_custom_field(
# If options is a list, then it's a dropdown
# If options is None, then it's a list of strings
is_list = isinstance(field_config.get("options"), list)
field_config["is_list"] = is_list or field_config.get("is_list", False)
field_config["is_list"] = (
is_list or field_config.get("is_list", False) or field_contains_list
)
if "name" in field_config:
warnings.warn(
@ -172,7 +178,7 @@ def extract_type_from_optional(field_type):
Returns:
str: The extracted type, or an empty string if no type was found.
"""
match = re.search(r"\[(.*?)\]", field_type)
match = re.search(r"\[(.*?)\]$", field_type)
return match[1] if match else None
@ -190,17 +196,16 @@ def build_frontend_node(custom_component: CustomComponent):
def update_attributes(frontend_node, template_config):
"""Update the display name and description of a frontend node"""
if "display_name" in template_config:
frontend_node["display_name"] = template_config["display_name"]
if "description" in template_config:
frontend_node["description"] = template_config["description"]
if "beta" in template_config:
frontend_node["beta"] = template_config["beta"]
if "documentation" in template_config:
frontend_node["documentation"] = template_config["documentation"]
attributes = [
"display_name",
"description",
"beta",
"documentation",
"output_types",
]
for attribute in attributes:
if attribute in template_config:
frontend_node[attribute] = template_config[attribute]
def build_field_config(custom_component: CustomComponent):
@ -338,7 +343,9 @@ def build_valid_menu(valid_components):
valid_menu[menu_name] = {}
for component in menu_item["components"]:
logger.debug(f"Building component: {component}")
logger.debug(
f"Building component: {component.get('name'), component.get('output_types')}"
)
try:
component_name = component["name"]
component_code = component["code"]

View file

@ -1,6 +1,6 @@
import json
from pathlib import Path
from langchain.schema import AgentAction
import json
from langflow.interface.run import (
build_sorted_vertices_with_caching,
get_memory_key,

View file

@ -1,5 +1,6 @@
import contextlib
import json
import orjson
import os
from typing import Optional, List
from pathlib import Path
@ -130,7 +131,7 @@ class Settings(BaseSettings):
if isinstance(getattr(self, key), list):
# value might be a '[something]' string
with contextlib.suppress(json.decoder.JSONDecodeError):
value = json.loads(str(value))
value = orjson.loads(str(value))
if isinstance(value, list):
for item in value:
if isinstance(item, Path):

View file

@ -1,5 +1,5 @@
import json
from typing import Optional
from langflow.database.models.base import orjson_dumps
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
@ -89,7 +89,7 @@ class LLMFrontendNode(FrontendNode):
if field.name == "config":
field.show = True
field.advanced = True
field.value = json.dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent=2)
field.value = orjson_dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent_2=True)
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:

View file

@ -1,6 +1,6 @@
import ast
import json
from typing import Optional
from langflow.database.models.base import orjson_dumps
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
@ -22,4 +22,4 @@ class UtilitiesFrontendNode(FrontendNode):
if isinstance(field.value, dict):
field.field_type = "code"
field.value = json.dumps(field.value, indent=4)
field.value = orjson_dumps(field.value)

View file

@ -2,7 +2,7 @@ import re
import inspect
import importlib
from functools import wraps
from typing import Optional, Dict, Any, Union
from typing import List, Optional, Dict, Any, Union
from docstring_parser import parse # type: ignore
@ -10,6 +10,7 @@ from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS
from langflow.utils import constants
from langflow.utils.logger import logger
from multiprocess import cpu_count # type: ignore
from langchain.schema import Document
def build_template_from_function(
@ -462,3 +463,12 @@ def get_number_of_workers(workers=None):
workers = (cpu_count() * 2) + 1
logger.debug(f"Number of workers: {workers}")
return workers
def build_loader_repr_from_documents(documents: List[Document]) -> str:
if documents:
avg_length = sum(len(doc.page_content) for doc in documents) / len(documents)
return f"""{len(documents)} documents
\nAvg. Document Length (characters): {int(avg_length)}
Documents: {documents[:3]}..."""
return "0 documents"

View file

@ -9,10 +9,16 @@ import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success";
import CrashErrorComponent from "./components/CrashErrorComponent";
import Header from "./components/headerComponent";
import FetchErrorComponent from "./components/fetchErrorComponent";
import LoadingComponent from "./components/loadingComponent";
import {
FETCH_ERROR_DESCRIPION,
FETCH_ERROR_MESSAGE,
} from "./constants/constants";
import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext";
import { TabsContext } from "./contexts/tabsContext";
import { typesContext } from "./contexts/typesContext";
import Router from "./routes";
export default function App() {
@ -25,6 +31,7 @@ export default function App() {
setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { hardReset } = useContext(TabsContext);
const {
errorData,
errorOpen,
@ -35,7 +42,9 @@ export default function App() {
successData,
successOpen,
setSuccessOpen,
loading,
} = useContext(alertContext);
const { fetchError } = useContext(typesContext);
// Initialize state variable for the list of alerts
const [alertsList, setAlertsList] = useState<
@ -133,8 +142,22 @@ export default function App() {
}}
FallbackComponent={CrashErrorComponent}
>
<Header />
<Router />
{loading ? (
<div className="loading-page-panel">
{fetchError ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : (
<LoadingComponent remSize={50} />
)}
</div>
) : (
<>
<Router />
</>
)}
</ErrorBoundary>
<div></div>
<div className="app-div" style={{ zIndex: 999 }}>

View file

@ -54,21 +54,9 @@ export const EditFlowSettings: React.FC<InputProps> = ({
}
}
setName(value);
setCurrentName(value);
};
const [currentName, setCurrentName] = useState(name);
const [currentDescription, setCurrentDescription] = useState(description);
useEffect(() => {
setCurrentName(name);
setCurrentDescription(description);
}, [name, description]);
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
flows.find((f) => f.id === tabId).description = event.target.value;
setCurrentDescription(flows.find((f) => f.id === tabId).description);
setDescription(event.target.value);
};
@ -89,7 +77,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onChange={handleNameChange}
type="text"
name="name"
value={currentName ?? ""}
value={name ?? ""}
placeholder="File name"
id="name"
maxLength={maxLength}
@ -104,7 +92,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
name="description"
id="description"
onChange={handleDescriptionChange}
value={currentDescription}
value={description}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}

View file

@ -0,0 +1,16 @@
import { fetchErrorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
export default function FetchErrorComponent({
message,
description,
}: fetchErrorComponentType) {
return (
<div role="status" className="m-auto flex flex-col items-center">
<IconComponent className={`h-16 w-16`} name="Unplug"></IconComponent>
<br></br>
<span className="text-lg text-almost-medium-blue">{message}</span>
<span className="text-lg text-almost-medium-blue">{description}</span>
</div>
);
}

View file

@ -7,5 +7,11 @@ export default function IconComponent({
iconColor,
}: IconComponentProps): JSX.Element {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
return <TargetIcon className={className} style={{ color: iconColor }} />;
return (
<TargetIcon
className={className}
style={{ color: iconColor }}
stroke-width={1.5}
/>
);
}

View file

@ -18,6 +18,9 @@ export default function InputListComponent({
}
}, [disabled]);
// @TODO Recursive Character Text Splitter - the value might be in string format, whereas the InputListComponent specifically requires an array format. To ensure smooth operation and prevent potential errors, it's crucial that we handle the conversion from a string to an array with the string as its element.
typeof value === 'string' ? value = [value] : value = value;
return (
<div
className={classNames(

View file

@ -1,6 +1,4 @@
type LoadingComponentProps = {
remSize: number;
};
import { LoadingComponentProps } from "../../types/components";
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
return (

View file

@ -509,3 +509,9 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"/api/v1/custom_component",
"/api/v1/validate/prompt",
];
export const skipNodeUpdate = ["CustomComponent"];
export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
export const FETCH_ERROR_DESCRIPION =
"Check if everything is working properly and try again.";

View file

@ -23,12 +23,16 @@ type alertContextType = {
pushNotificationList: (Object: AlertItemType) => void;
clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
};
//initial values to alertContextType
const initialValue: alertContextType = {
errorData: { title: "", list: [] },
setErrorData: () => {},
loading: true,
setLoading: () => {},
errorOpen: false,
setErrorOpen: () => {},
noticeData: { title: "", link: "" },
@ -55,6 +59,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
list?: Array<string>;
}>({ title: "", list: [] });
const [errorOpen, setErrorOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [noticeData, setNoticeDataState] = useState<{
title: string;
link?: string;
@ -141,6 +146,8 @@ export function AlertProvider({ children }: { children: ReactNode }) {
removeFromNotificationList,
clearNotificationList,
notificationList,
loading,
setLoading,
pushNotificationList,
setNotificationCenter,
notificationCenter,

View file

@ -16,17 +16,17 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
<TooltipProvider>
<ReactFlowProvider>
<DarkProvider>
<TypesProvider>
<LocationProvider>
<AlertProvider>
<AlertProvider>
<TypesProvider>
<LocationProvider>
<SSEProvider>
<TabsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider>
</TabsProvider>
</SSEProvider>
</AlertProvider>
</LocationProvider>
</TypesProvider>
</LocationProvider>
</TypesProvider>
</AlertProvider>
</DarkProvider>
</ReactFlowProvider>
</TooltipProvider>

View file

@ -9,6 +9,7 @@ import {
} from "react";
import { addEdge } from "reactflow";
import ShortUniqueId from "short-unique-id";
import { skipNodeUpdate } from "../constants/constants";
import {
deleteFlowFromDatabase,
downloadFlowsFromDatabase,
@ -163,6 +164,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
function processFlowNodes(flow) {
if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node: NodeType) => {
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });
@ -506,6 +508,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const updateNodes = (nodes, edges) => {
nodes.forEach((node) => {
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });

View file

@ -1,8 +1,15 @@
import { createContext, ReactNode, useEffect, useState } from "react";
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { Node } from "reactflow";
import { getAll } from "../controllers/API";
import { getAll, getHealth } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
import { alertContext } from "./alertContext";
//context to share types adn functions from nodes to flow
@ -16,6 +23,8 @@ const initialValue: typesContextType = {
setTemplates: () => {},
data: {},
setData: () => {},
setFetchError: () => {},
fetchError: false,
};
export const typesContext = createContext<typesContextType>(initialValue);
@ -25,13 +34,10 @@ export function TypesProvider({ children }: { children: ReactNode }) {
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [templates, setTemplates] = useState({});
const [data, setData] = useState({});
const [fetchError, setFetchError] = useState(false);
const { setLoading } = useContext(alertContext);
useEffect(() => {
let delay = 1000; // Start delay of 1 second
let intervalId = null;
let retryCount = 0; // Count of retry attempts
const maxRetryCount = 5; // Max retry attempts
// We will keep a flag to handle the case where the component is unmounted before the API call resolves.
let isMounted = true;
@ -39,7 +45,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
try {
const result = await getAll();
// Make sure to only update the state if the component is still mounted.
if (isMounted) {
if (isMounted && result?.status === 200) {
setLoading(false);
setData(result.data);
setTemplates(
Object.keys(result.data).reduce((acc, curr) => {
@ -68,22 +75,15 @@ export function TypesProvider({ children }: { children: ReactNode }) {
}, {})
);
}
// Clear the interval if successful.
clearInterval(intervalId);
} catch (error) {
console.error("An error has occurred while fetching types.");
await getHealth().catch((e) => {
setFetchError(true);
});
}
}
// Start the initial interval.
intervalId = setInterval(getTypes, delay);
return () => {
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
clearInterval(intervalId);
// Indicate that the component has been unmounted.
isMounted = false;
};
getTypes();
}, []);
function deleteNode(idx: string) {
@ -108,6 +108,8 @@ export function TypesProvider({ children }: { children: ReactNode }) {
templates,
data,
setData,
fetchError,
setFetchError,
}}
>
{children}

View file

@ -6,6 +6,7 @@ import { TabsContext } from "../../contexts/tabsContext";
import { useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
export default function CommunityPage() {
@ -42,61 +43,65 @@ export default function CommunityPage() {
handleExamples();
}, []);
return (
<div className="community-page-arrangement">
<div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" />
Community Examples
<>
<Header />
<div className="community-page-arrangement">
<div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" />
Community Examples
</span>
<div className="community-page-nav-button">
<a
href="https://github.com/logspace-ai/langflow_examples"
target="_blank"
rel="noreferrer"
>
<Button variant="primary">
<IconComponent
name="GithubIcon"
className="main-page-nav-button"
/>
Add Your Example
</Button>
</a>
</div>
</div>
<span className="community-page-description-text">
Discover and learn from shared examples by the Langflow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span>
<div className="community-page-nav-button">
<a
href="https://github.com/logspace-ai/langflow_examples"
target="_blank"
rel="noreferrer"
>
<Button variant="primary">
<IconComponent
name="GithubIcon"
className="main-page-nav-button"
<div className="community-pages-flows-panel">
{!loadingExamples &&
examples.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
name="GitFork"
className="main-page-nav-button"
/>
Fork Example
</Button>
}
/>
Add Your Example
</Button>
</a>
))}
</div>
</div>
<span className="community-page-description-text">
Discover and learn from shared examples by the Langflow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span>
<div className="community-pages-flows-panel">
{!loadingExamples &&
examples.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
name="GitFork"
className="main-page-nav-button"
/>
Fork Example
</Button>
}
/>
))}
</div>
</div>
</>
);
}

View file

@ -35,7 +35,13 @@ const nodeTypes = {
genericNode: GenericNode,
};
export default function Page({ flow }: { flow: FlowType }) {
export default function Page({
flow,
view,
}: {
flow: FlowType;
view?: boolean;
}) {
let {
updateFlow,
uploadFlow,
@ -357,7 +363,7 @@ export default function Page({ flow }: { flow: FlowType }) {
return (
<div className="flex h-full overflow-hidden">
<ExtraSidebar />
{!view && <ExtraSidebar />}
{/* Main area */}
<main className="flex flex-1">
{/* Primary column */}
@ -399,14 +405,21 @@ export default function Page({ flow }: { flow: FlowType }) {
className="theme-attribution"
minZoom={0.01}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
>
<Background className="" />
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
{!view && (
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
[&>button]:border-b-border hover:[&>button]:bg-border"
></Controls>
></Controls>
)}
</ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
{!view && (
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
)}
</div>
) : (
<></>

View file

@ -1,5 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Header from "../../components/headerComponent";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "./components/PageComponent";
@ -22,20 +23,23 @@ export default function FlowPage() {
}, []);
return (
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page flow={flows.find((flow) => flow.id === tabId)} />
)}
<a
target={"_blank"}
href="https://logspace.ai/"
className="logspace-page-icon"
>
{version && <div className="mt-1"> Langflow v{version}</div>}
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
</a>
</div>
<>
<Header />
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page flow={flows.find((flow) => flow.id === tabId)} />
)}
<a
target={"_blank"}
href="https://logspace.ai/"
className="logspace-page-icon"
>
{version && <div className="mt-1"> Langflow v{version}</div>}
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
</a>
</div>
</>
);
}

View file

@ -2,6 +2,7 @@ import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
@ -17,74 +18,77 @@ export default function HomePage() {
// Personal flows display
return (
<div className="main-page-panel">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
<>
<Header />
<div className="main-page-panel">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
</>
);
}

View file

@ -0,0 +1,33 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
const { flows, tabId, setTabId } = useContext(TabsContext);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id);
}, [id]);
// Initialize state variable for the version
const [version, setVersion] = useState("");
useEffect(() => {
getVersion().then((data) => {
setVersion(data.version);
});
}, []);
return (
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page view flow={flows.find((flow) => flow.id === tabId)} />
)}
</div>
);
}

View file

@ -2,6 +2,7 @@ import { Route, Routes } from "react-router-dom";
import CommunityPage from "./pages/CommunityPage";
import FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage";
import ViewPage from "./pages/ViewPage";
const Router = () => {
return (
@ -10,6 +11,7 @@ const Router = () => {
<Route path="/community" element={<CommunityPage />} />
<Route path="/flow/:id/">
<Route path="" element={<FlowPage />} />
<Route path="view" element={<ViewPage />} />
</Route>
<Route path="*" element={<HomePage />} />
</Routes>

View file

@ -192,6 +192,10 @@
@apply flex w-full;
}
.loading-page-panel {
@apply h-full w-full flex justify-center items-center bg-muted;
}
.main-page-panel {
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
}

View file

@ -171,3 +171,11 @@ export type IconComponentProps = {
export interface languageMap {
[key: string]: string | undefined;
}
export type fetchErrorComponentType = {
message: string;
description: string;
};
export type LoadingComponentProps = {
remSize: number;
};

View file

@ -15,4 +15,6 @@ export type typesContextType = {
setTemplates: (newState: {}) => void;
data: typeof data;
setData: (newState: {}) => void;
fetchError: boolean;
setFetchError: (newState: boolean) => void;
};

View file

@ -56,6 +56,7 @@ import {
TerminalSquare,
Trash2,
Undo,
Unplug,
Upload,
Users2,
Variable,
@ -275,4 +276,5 @@ export const nodeIconsLucide = {
Upload,
MessageSquare,
MoreHorizontal,
Unplug,
};

View file

@ -1,4 +1,6 @@
import json
from langflow.database.models.base import orjson_dumps
import orjson
from langflow.graph import Graph
import pytest
@ -63,9 +65,9 @@ def test_cache_size_limit(basic_data_graph):
nodes = modified_data_graph["nodes"]
node_id = nodes[0]["id"]
# Now we replace all instances ode node_id with a new id in the json
json_string = json.dumps(modified_data_graph)
json_string = orjson_dumps(modified_data_graph)
modified_json_string = json_string.replace(node_id, f"{node_id}_{i}")
modified_data_graph_new_id = json.loads(modified_json_string)
modified_data_graph_new_id = orjson.loads(modified_json_string)
build_langchain_object_with_caching(modified_data_graph_new_id)
assert len(build_langchain_object_with_caching.cache) == 10

View file

@ -1,11 +1,12 @@
import json
from fastapi.encoders import jsonable_encoder
from langflow.database.models.base import orjson_dumps
import orjson
import pytest
from uuid import UUID, uuid4
from sqlalchemy.orm import Session
from fastapi.testclient import TestClient
from fastapi.encoders import jsonable_encoder
from langflow.api.v1.schemas import FlowListCreate
from langflow.database.models.flow import Flow, FlowCreate, FlowUpdate
@ -23,7 +24,7 @@ def json_style():
# color: str = Field(index=True)
# emoji: str = Field(index=False)
# flow_id: UUID = Field(default=None, foreign_key="flow.id")
return json.dumps(
return orjson_dumps(
{
"color": "red",
"emoji": "👍",
@ -32,7 +33,7 @@ def json_style():
def test_create_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict())
@ -48,7 +49,7 @@ def test_create_flow(client: TestClient, json_flow: str):
def test_read_flows(client: TestClient, json_flow: str):
flow_data = json.loads(json_flow)
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict())
@ -89,7 +90,7 @@ def test_read_flows(client: TestClient, json_flow: str):
def test_read_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict())
@ -115,7 +116,7 @@ def test_read_flow(client: TestClient, json_flow: str):
def test_update_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
@ -136,7 +137,7 @@ def test_update_flow(client: TestClient, json_flow: str):
def test_delete_flow(client: TestClient, json_flow: str):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict())
@ -147,7 +148,7 @@ def test_delete_flow(client: TestClient, json_flow: str):
def test_create_flows(client: TestClient, session: Session, json_flow: str):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
@ -172,7 +173,7 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str):
def test_upload_file(client: TestClient, session: Session, json_flow: str):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
@ -181,7 +182,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str):
FlowCreate(name="Flow 2", description="description", data=data),
]
)
file_contents = json.dumps(flow_list.dict())
file_contents = orjson_dumps(flow_list.dict())
response = client.post(
"api/v1/flows/upload/",
files={"file": ("examples.json", file_contents, "application/json")},
@ -200,7 +201,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str):
def test_download_file(client: TestClient, session: Session, json_flow):
flow = json.loads(json_flow)
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
@ -241,7 +242,7 @@ def test_get_nonexistent_flow(client: TestClient):
def test_update_flow_idempotency(client: TestClient, json_flow: str):
flow_data = json.loads(json_flow)
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow_data = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow_data.dict())
@ -253,7 +254,7 @@ def test_update_flow_idempotency(client: TestClient, json_flow: str):
def test_update_nonexistent_flow(client: TestClient, json_flow: str):
flow_data = json.loads(json_flow)
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
uuid = uuid4()
updated_flow = FlowCreate(

View file

@ -1,13 +1,12 @@
from fastapi.testclient import TestClient
from langflow.settings import settings
def test_llms_settings(client: TestClient):
response = client.get("api/v1/all")
assert response.status_code == 200
json_response = response.json()
llms = json_response["llms"]
assert set(llms.keys()) == set(settings.LLMS)
# def test_llms_settings(client: TestClient):
# response = client.get("api/v1/all")
# assert response.status_code == 200
# json_response = response.json()
# llms = json_response["llms"]
# assert set(llms.keys()) == set(settings.LLMS)
# def test_hugging_face_hub(client: TestClient):

View file

@ -1,5 +1,4 @@
import json
import pytest
from langchain.chains.base import Chain
from langflow.processing.process import load_flow_from_json