Update package versions, workflows, LLMChain and Graph sorting (#1674)
* Update package versions in pyproject.toml and poetry.lock files * Update poetry caching action and workflows * Update poetry caching action and workflows * Refactor LLMChainComponent build method in LLMChain.py * Update poetry install command in Makefile * Refactor Makefile to remove redundant install_backend targets * Fix codespell issues in project * Update package versions and dependencies * Fix import order in chat_io.spec.ts, headerComponent/index.tsx, and chatMessage/index.tsx * Update ruff command in Makefile and fix poetry cache reuse in Dockerfile * Refactor ServiceManager class in manager.py to handle default service factories * Fix typo in DOWNLOAD_WEBHOOK_URL variable assignment * Refactor cache_service tests in test_cache_manager.py * Add pytest-profiling * Update Makefile to run unit tests with parallel execution * Refactor ServiceManager class in manager.py to handle default service factories * Refactor node_name condition in Graph class to use "Listen" instead of "GetNotified" * Refactor file paths in tests/conftest.py for better readability and maintainability * Sort vertices in each layer by dependency in Graph class * Refactor variable declaration in SessionService class to use type hinting * Refactor make tests command in python_test.yml workflow * Refactor file paths in tests/conftest.py for better readability and maintainability * Refactor imports in tests/conftest.py to include sqlmodel.Session and related dependencies * Refactor file paths in tests/conftest.py to include available files in error message * Refactor file paths in tests/conftest.py to include available files in error message * Refactor file paths in tests/conftest.py to fix typo in BasicChatwithPromptAndHistory.json
This commit is contained in:
parent
836ac08d80
commit
e582535bb0
23 changed files with 396 additions and 286 deletions
94
.github/actions/poetry_caching/action.yml
vendored
Normal file
94
.github/actions/poetry_caching/action.yml
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# An action for setting up poetry install with caching.
|
||||
# Using a custom action since the default action does not
|
||||
# take poetry install groups into account.
|
||||
# Action code from:
|
||||
# https://github.com/actions/setup-python/issues/505#issuecomment-1273013236
|
||||
# Copy of https://github.com/langchain-ai/langchain/blob/2f8dd1a1619f25daa4737df4d378b1acd6ff83c4/.github/actions/poetry_setup/action.yml
|
||||
name: poetry-install-with-caching
|
||||
description: Poetry install with support for caching of dependency groups.
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: Python version, supporting MAJOR.MINOR only
|
||||
required: true
|
||||
|
||||
poetry-version:
|
||||
description: Poetry version
|
||||
required: true
|
||||
|
||||
cache-key:
|
||||
description: Cache key to use for manual handling of caching
|
||||
required: true
|
||||
|
||||
working-directory:
|
||||
description: Directory whose poetry.lock file should be cached
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/setup-python@v5
|
||||
name: Setup python ${{ inputs.python-version }}
|
||||
id: setup-python
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: cache-bin-poetry
|
||||
name: Cache Poetry binary - Python ${{ inputs.python-version }}
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "1"
|
||||
with:
|
||||
path: |
|
||||
/opt/pipx/venvs/poetry
|
||||
# This step caches the poetry installation, so make sure it's keyed on the poetry version as well.
|
||||
key: bin-poetry-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-${{ inputs.poetry-version }}
|
||||
|
||||
- name: Refresh shell hashtable and fixup softlinks
|
||||
if: steps.cache-bin-poetry.outputs.cache-hit == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
POETRY_VERSION: ${{ inputs.poetry-version }}
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
run: |
|
||||
set -eux
|
||||
|
||||
# Refresh the shell hashtable, to ensure correct `which` output.
|
||||
hash -r
|
||||
|
||||
# `actions/cache@v3` doesn't always seem able to correctly unpack softlinks.
|
||||
# Delete and recreate the softlinks pipx expects to have.
|
||||
rm /opt/pipx/venvs/poetry/bin/python
|
||||
cd /opt/pipx/venvs/poetry/bin
|
||||
ln -s "$(which "python$PYTHON_VERSION")" python
|
||||
chmod +x python
|
||||
cd /opt/pipx_bin/
|
||||
ln -s /opt/pipx/venvs/poetry/bin/poetry poetry
|
||||
chmod +x poetry
|
||||
|
||||
# Ensure everything got set up correctly.
|
||||
/opt/pipx/venvs/poetry/bin/python --version
|
||||
/opt/pipx_bin/poetry --version
|
||||
|
||||
- name: Install poetry
|
||||
if: steps.cache-bin-poetry.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
env:
|
||||
POETRY_VERSION: ${{ inputs.poetry-version }}
|
||||
PYTHON_VERSION: ${{ inputs.python-version }}
|
||||
# Install poetry using the python version installed by setup-python step.
|
||||
run: pipx install "poetry==$POETRY_VERSION" --python '${{ steps.setup-python.outputs.python-path }}' --verbose
|
||||
|
||||
- name: Restore pip and poetry cached dependencies
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "4"
|
||||
WORKDIR: ${{ inputs.working-directory == '' && '.' || inputs.working-directory }}
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.cache/pypoetry/virtualenvs
|
||||
~/.cache/pypoetry/cache
|
||||
~/.cache/pypoetry/artifacts
|
||||
${{ env.WORKDIR }}/.venv
|
||||
key: py-deps-${{ runner.os }}-${{ runner.arch }}-py-${{ inputs.python-version }}-poetry-${{ inputs.poetry-version }}-${{ inputs.cache-key }}-${{ hashFiles(format('{0}/**/poetry.lock', env.WORKDIR)) }}
|
||||
22
.github/workflows/lint.yml
vendored
22
.github/workflows/lint.yml
vendored
|
|
@ -26,20 +26,24 @@ jobs:
|
|||
- "3.11"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install poetry
|
||||
run: |
|
||||
pipx install poetry==$POETRY_VERSION
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
id: setup-python
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
uses: "./.github/actions/poetry_caching"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: poetry
|
||||
poetry-version: ${{ env.POETRY_VERSION }}
|
||||
cache-key: ${{ runner.os }}-poetry-${{ env.POETRY_VERSION }}-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
poetry env use ${{ matrix.python-version }}
|
||||
poetry install
|
||||
if: ${{ steps.setup-python.outputs.cache-hit != 'true' }}
|
||||
- name: Analysing the code with our lint
|
||||
- name: Get .mypy_cache to speed up mypy
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2"
|
||||
with:
|
||||
path: |
|
||||
./.mypy_cache
|
||||
key: ${{ runner.os }}-mypy-${{ hashFiles('**/pyproject.toml') }}
|
||||
- name: Lint check
|
||||
run: |
|
||||
make lint
|
||||
|
|
|
|||
11
.github/workflows/python_test.yml
vendored
11
.github/workflows/python_test.yml
vendored
|
|
@ -29,19 +29,16 @@ jobs:
|
|||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install poetry
|
||||
run: pipx install poetry==$POETRY_VERSION
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
id: setup-python
|
||||
- name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
|
||||
uses: "./.github/actions/poetry_caching"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "poetry"
|
||||
poetry-version: ${{ env.POETRY_VERSION }}
|
||||
cache-key: ${{ runner.os }}-poetry-${{ env.POETRY_VERSION }}-${{ hashFiles('**/poetry.lock') }}
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
poetry env use ${{ matrix.python-version }}
|
||||
poetry install
|
||||
if: ${{ steps.setup-python.outputs.cache-hit != 'true' }}
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
make tests
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -264,4 +264,5 @@ scratchpad*
|
|||
chroma*/*
|
||||
stuff/*
|
||||
src/frontend/playwright-report/index.html
|
||||
*.bak
|
||||
*.bak
|
||||
prof/*
|
||||
20
Makefile
20
Makefile
|
|
@ -8,6 +8,14 @@ env ?= .env
|
|||
open_browser ?= true
|
||||
path = src/backend/base/langflow/frontend
|
||||
|
||||
codespell:
|
||||
@poetry install --with spelling
|
||||
poetry run codespell --toml pyproject.toml
|
||||
|
||||
fix_codespell:
|
||||
@poetry install --with spelling
|
||||
poetry run codespell --toml pyproject.toml --write
|
||||
|
||||
setup_poetry:
|
||||
pipx install poetry
|
||||
|
||||
|
|
@ -39,20 +47,16 @@ coverage:
|
|||
|
||||
# allow passing arguments to pytest
|
||||
tests:
|
||||
@make install_backend
|
||||
|
||||
poetry run pytest tests --instafail $(args)
|
||||
# Use like:
|
||||
|
||||
format:
|
||||
poetry run ruff . --fix
|
||||
poetry run ruff check . --fix
|
||||
poetry run ruff format .
|
||||
cd src/frontend && npm run format
|
||||
|
||||
lint:
|
||||
make install_backend
|
||||
poetry run mypy --namespace-packages -p "langflow"
|
||||
poetry run ruff . --fix
|
||||
|
||||
install_frontend:
|
||||
cd src/frontend && npm install
|
||||
|
|
@ -129,12 +133,12 @@ frontendc:
|
|||
make run_frontend
|
||||
|
||||
install_backend:
|
||||
@echo 'Setting up the environment'
|
||||
@make setup_env
|
||||
@echo 'Installing backend dependencies'
|
||||
@poetry install --extras deploy
|
||||
@poetry install
|
||||
|
||||
backend:
|
||||
@echo 'Setting up the environment'
|
||||
@make setup_env
|
||||
make install_backend
|
||||
@-kill -9 `lsof -t -i:7860`
|
||||
ifdef login
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ RUN apt-get update \
|
|||
|
||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
||||
# The --mount will mount the buildx cache directory to where
|
||||
# Poetry and Pip store their cache so that they can re-use it
|
||||
# Poetry and Pip store their cache so that they can reuse it
|
||||
RUN --mount=type=cache,target=/root/.cache \
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ RUN apt-get update \
|
|||
|
||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
||||
# The --mount will mount the buildx cache directory to where
|
||||
# Poetry and Pip store their cache so that they can re-use it
|
||||
# Poetry and Pip store their cache so that they can reuse it
|
||||
RUN --mount=type=cache,target=/root/.cache \
|
||||
curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
|
|
|
|||
98
poetry.lock
generated
98
poetry.lock
generated
|
|
@ -1130,6 +1130,23 @@ prompt-toolkit = ">=3.0.36"
|
|||
[package.extras]
|
||||
testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "codespell"
|
||||
version = "2.2.6"
|
||||
description = "Codespell"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"},
|
||||
{file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
|
||||
hard-encoding-detection = ["chardet"]
|
||||
toml = ["tomli"]
|
||||
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
|
||||
|
||||
[[package]]
|
||||
name = "cohere"
|
||||
version = "5.2.5"
|
||||
|
|
@ -2659,6 +2676,17 @@ files = [
|
|||
httpx = ">=0.23,<0.28"
|
||||
pydantic = ">=1.10,<3"
|
||||
|
||||
[[package]]
|
||||
name = "gprof2dot"
|
||||
version = "2022.7.29"
|
||||
description = "Generate a dot graph from the output of several profilers."
|
||||
optional = false
|
||||
python-versions = ">=2.7"
|
||||
files = [
|
||||
{file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"},
|
||||
{file = "gprof2dot-2022.7.29.tar.gz", hash = "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.0.3"
|
||||
|
|
@ -3930,13 +3958,13 @@ url = "src/backend/base"
|
|||
|
||||
[[package]]
|
||||
name = "langfuse"
|
||||
version = "2.23.2"
|
||||
version = "2.24.0"
|
||||
description = "A client library for accessing langfuse"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langfuse-2.23.2-py3-none-any.whl", hash = "sha256:591811d36fe42b26451615686ea91c04d96afd247ac023818b1ca72861e537a6"},
|
||||
{file = "langfuse-2.23.2.tar.gz", hash = "sha256:11df6c6ff6ca5f6a079836f2530d5f7dcbe11d7058ae16efcafbf8275b0e14b6"},
|
||||
{file = "langfuse-2.24.0-py3-none-any.whl", hash = "sha256:356e8260f093470f09ed8aabad769bae6e9eb1e8a8682f53f1d135c69e07d0d9"},
|
||||
{file = "langfuse-2.24.0.tar.gz", hash = "sha256:c2f9f4c66578ea70da20c2f1bf6ac468b349471b9f2644f95add57aa00817827"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -3986,13 +4014,13 @@ regex = ["regex"]
|
|||
|
||||
[[package]]
|
||||
name = "litellm"
|
||||
version = "1.34.40"
|
||||
version = "1.34.41"
|
||||
description = "Library to easily interface with LLM API providers"
|
||||
optional = false
|
||||
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
|
||||
files = [
|
||||
{file = "litellm-1.34.40-py3-none-any.whl", hash = "sha256:ad6d321eec9819d1697223f5c528ca5c5f2476944bb2afc691cf8517804777e7"},
|
||||
{file = "litellm-1.34.40.tar.gz", hash = "sha256:99faea82cc8bfa1ca6e3063adebfd9e65ecc60b00f4de9a47bda0559d6c9f637"},
|
||||
{file = "litellm-1.34.41-py3-none-any.whl", hash = "sha256:ec6b1fb7f27178ad36f3be64e2f0fb73a637c0d7c128d5aa1d845414cc21be66"},
|
||||
{file = "litellm-1.34.41.tar.gz", hash = "sha256:e8ad1a0b9cf4abea0471394db8c95f479e94e271bc1ce3d98204e1a6ed917e93"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -7185,6 +7213,25 @@ pytest = ">=6.2.5"
|
|||
[package.extras]
|
||||
dev = ["pre-commit", "pytest-asyncio", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-profiling"
|
||||
version = "1.7.0"
|
||||
description = "Profiling plugin for py.test"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"},
|
||||
{file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
gprof2dot = "*"
|
||||
pytest = "*"
|
||||
six = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest-virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-sugar"
|
||||
version = "1.0.0"
|
||||
|
|
@ -7999,28 +8046,28 @@ msg-parse = ["extract-msg (>=0.27)"]
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.2.2"
|
||||
version = "0.3.5"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"},
|
||||
{file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"},
|
||||
{file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"},
|
||||
{file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"},
|
||||
{file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"},
|
||||
{file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"},
|
||||
{file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"},
|
||||
{file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"},
|
||||
{file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"},
|
||||
{file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"},
|
||||
{file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"},
|
||||
{file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"},
|
||||
{file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"},
|
||||
{file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"},
|
||||
{file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"},
|
||||
{file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"},
|
||||
{file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"},
|
||||
{file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"},
|
||||
{file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -10256,11 +10303,10 @@ test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
|||
testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
|
||||
[extras]
|
||||
all = []
|
||||
deploy = ["celery", "flower", "redis"]
|
||||
local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.10,<3.12"
|
||||
content-hash = "0d658da1ee75640f74d7335685760091df2a452e84eef81dcda2590dd4044e92"
|
||||
content-hash = "9dd152b30031767c522c77e2ad5fc4597a8d1590b13968af143bd382e056b2a1"
|
||||
|
|
|
|||
|
|
@ -85,10 +85,10 @@ langchain-openai = "^0.1.1"
|
|||
[tool.poetry.group.dev.dependencies]
|
||||
types-redis = "^4.6.0.5"
|
||||
ipykernel = "^6.29.0"
|
||||
mypy = "^1.8.0"
|
||||
ruff = "^0.2.1"
|
||||
mypy = "^1.9.0"
|
||||
ruff = "^0.3.5"
|
||||
httpx = "*"
|
||||
pytest = "^8.0.0"
|
||||
pytest = "^8.1.0"
|
||||
types-requests = "^2.31.0"
|
||||
requests = "^2.31.0"
|
||||
pytest-cov = "^4.1.0"
|
||||
|
|
@ -106,11 +106,23 @@ pytest-sugar = "^1.0.0"
|
|||
respx = "^0.21.1"
|
||||
pytest-instafail = "^0.5.0"
|
||||
pytest-asyncio = "^0.23.0"
|
||||
pytest-profiling = "^1.7.0"
|
||||
|
||||
[tool.poetry.extras]
|
||||
deploy = ["celery", "redis", "flower"]
|
||||
local = ["llama-cpp-python", "sentence-transformers", "ctransformers"]
|
||||
all = ["deploy", "local"]
|
||||
|
||||
|
||||
[tool.poetry.group.spelling]
|
||||
optional = true
|
||||
|
||||
[tool.poetry.group.spelling.dependencies]
|
||||
codespell = "^2.2.6"
|
||||
|
||||
[tool.codespell]
|
||||
skip = '.git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,*.trig'
|
||||
# Ignore latin etc
|
||||
ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ Revises: 1a110b568907
|
|||
Create Date: 2024-04-10 19:17:22.820455
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.chains import LLMChain
|
||||
from langchain.chains.llm import LLMChain
|
||||
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, BasePromptTemplate, Text
|
||||
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.prompts import PromptTemplate
|
||||
|
||||
|
||||
class LLMChainComponent(CustomComponent):
|
||||
|
|
@ -19,10 +20,11 @@ class LLMChainComponent(CustomComponent):
|
|||
|
||||
def build(
|
||||
self,
|
||||
prompt: BasePromptTemplate,
|
||||
template: Text,
|
||||
llm: BaseLanguageModel,
|
||||
memory: Optional[BaseMemory] = None,
|
||||
) -> Text:
|
||||
prompt = PromptTemplate.from_template(template)
|
||||
runnable = LLMChain(prompt=prompt, llm=llm, memory=memory)
|
||||
result_dict = runnable.invoke({})
|
||||
output_key = runnable.output_key
|
||||
|
|
|
|||
|
|
@ -918,7 +918,7 @@ class Graph:
|
|||
return ChatVertex
|
||||
elif node_name in ["ShouldRunNext"]:
|
||||
return RoutingVertex
|
||||
elif node_name in ["SharedState", "Notify", "GetNotified"]:
|
||||
elif node_name in ["SharedState", "Notify", "Listen"]:
|
||||
return StateVertex
|
||||
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
|
||||
return lazy_load_vertex_dict.VERTEX_TYPE_MAP[node_base_type]
|
||||
|
|
@ -1130,6 +1130,34 @@ class Graph:
|
|||
|
||||
return vertices_layers
|
||||
|
||||
def sort_layer_by_dependency(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||
"""Sorts the vertices in each layer by dependency, ensuring no vertex depends on a subsequent vertex."""
|
||||
sorted_layers = []
|
||||
|
||||
for layer in vertices_layers:
|
||||
sorted_layer = self._sort_single_layer_by_dependency(layer)
|
||||
sorted_layers.append(sorted_layer)
|
||||
|
||||
return sorted_layers
|
||||
|
||||
def _sort_single_layer_by_dependency(self, layer: List[str]) -> List[str]:
|
||||
"""Sorts a single layer by dependency using a stable sorting method."""
|
||||
# Build a map of each vertex to its index in the layer for quick lookup.
|
||||
index_map = {vertex: index for index, vertex in enumerate(layer)}
|
||||
# Create a sorted copy of the layer based on dependency order.
|
||||
sorted_layer = sorted(layer, key=lambda vertex: self._max_dependency_index(vertex, index_map), reverse=True)
|
||||
|
||||
return sorted_layer
|
||||
|
||||
def _max_dependency_index(self, vertex_id: str, index_map: Dict[str, int]) -> int:
|
||||
"""Finds the highest index a given vertex's dependencies occupy in the same layer."""
|
||||
vertex = self.get_vertex(vertex_id)
|
||||
max_index = -1
|
||||
for successor in vertex.successors: # Assuming vertex.successors is a list of successor vertex identifiers.
|
||||
if successor.id in index_map:
|
||||
max_index = max(max_index, index_map[successor.id])
|
||||
return max_index
|
||||
|
||||
def sort_vertices(
|
||||
self,
|
||||
stop_component_id: Optional[str] = None,
|
||||
|
|
@ -1151,6 +1179,9 @@ class Graph:
|
|||
vertices_layers = self.layered_topological_sort(vertices)
|
||||
vertices_layers = self.sort_by_avg_build_time(vertices_layers)
|
||||
# vertices_layers = self.sort_chat_inputs_first(vertices_layers)
|
||||
# Now we should sort each layer in a way that we make sure
|
||||
# vertex V does not depend on vertex V+1
|
||||
vertices_layers = self.sort_layer_by_dependency(vertices_layers)
|
||||
self.increment_run_count()
|
||||
self._sorted_vertices_layers = vertices_layers
|
||||
first_layer = vertices_layers[0]
|
||||
|
|
|
|||
|
|
@ -5,17 +5,6 @@ from loguru import logger
|
|||
from langflow.graph import Graph
|
||||
|
||||
|
||||
async def build_sorted_vertices(data_graph, flow_id: str) -> Tuple[Graph, Dict]:
|
||||
"""
|
||||
Build langchain object from data_graph.
|
||||
"""
|
||||
|
||||
logger.debug("Building langchain object")
|
||||
graph = Graph.from_payload(data_graph, flow_id=flow_id)
|
||||
|
||||
return graph, {}
|
||||
|
||||
|
||||
def get_memory_key(langchain_object):
|
||||
"""
|
||||
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING, Generator
|
||||
|
||||
from langflow.services import ServiceType, service_manager
|
||||
from langflow.services.schema import ServiceType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlmodel import Session
|
||||
|
|
@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
|||
from langflow.services.variable.service import VariableService
|
||||
|
||||
|
||||
def get_service(service_type: ServiceType):
|
||||
def get_service(service_type: ServiceType, default=None):
|
||||
"""
|
||||
Retrieves the service instance for the given service type.
|
||||
|
||||
|
|
@ -32,7 +32,13 @@ def get_service(service_type: ServiceType):
|
|||
Any: The service instance.
|
||||
|
||||
"""
|
||||
return service_manager.get(service_type) # type: ignore
|
||||
from langflow.services.manager import service_manager
|
||||
|
||||
if not service_manager.factories:
|
||||
#! This is a workaround to ensure that the service manager is initialized
|
||||
#! Not optimal, but it works for now
|
||||
service_manager.register_factories()
|
||||
return service_manager.get(service_type, default) # type: ignore
|
||||
|
||||
|
||||
def get_state_service() -> "StateService":
|
||||
|
|
@ -42,7 +48,9 @@ def get_state_service() -> "StateService":
|
|||
Returns:
|
||||
The StateService instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.STATE_SERVICE) # type: ignore
|
||||
from langflow.services.state.factory import StateServiceFactory
|
||||
|
||||
return get_service(ServiceType.STATE_SERVICE, StateServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_socket_service() -> "SocketIOService":
|
||||
|
|
@ -52,7 +60,7 @@ def get_socket_service() -> "SocketIOService":
|
|||
Returns:
|
||||
SocketIOService: The SocketIOService instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.SOCKETIO_SERVICE) # type: ignore
|
||||
return get_service(ServiceType.SOCKETIO_SERVICE) # type: ignore
|
||||
|
||||
|
||||
def get_storage_service() -> "StorageService":
|
||||
|
|
@ -62,7 +70,9 @@ def get_storage_service() -> "StorageService":
|
|||
Returns:
|
||||
The storage service instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.STORAGE_SERVICE) # type: ignore
|
||||
from langflow.services.storage.factory import StorageServiceFactory
|
||||
|
||||
return get_service(ServiceType.STORAGE_SERVICE, default=StorageServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_variable_service() -> "VariableService":
|
||||
|
|
@ -73,7 +83,9 @@ def get_variable_service() -> "VariableService":
|
|||
The VariableService instance.
|
||||
|
||||
"""
|
||||
return service_manager.get(ServiceType.VARIABLE_SERVICE) # type: ignore
|
||||
from langflow.services.variable.factory import VariableServiceFactory
|
||||
|
||||
return get_service(ServiceType.VARIABLE_SERVICE, VariableServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_plugins_service() -> "PluginService":
|
||||
|
|
@ -83,7 +95,7 @@ def get_plugins_service() -> "PluginService":
|
|||
Returns:
|
||||
PluginService: The PluginService instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.PLUGIN_SERVICE) # type: ignore
|
||||
return get_service(ServiceType.PLUGIN_SERVICE) # type: ignore
|
||||
|
||||
|
||||
def get_settings_service() -> "SettingsService":
|
||||
|
|
@ -98,14 +110,9 @@ def get_settings_service() -> "SettingsService":
|
|||
Raises:
|
||||
ValueError: If the service cannot be retrieved or initialized.
|
||||
"""
|
||||
try:
|
||||
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
|
||||
except ValueError:
|
||||
# initialize settings service
|
||||
from langflow.services.manager import initialize_settings_service
|
||||
from langflow.services.settings.factory import SettingsServiceFactory
|
||||
|
||||
initialize_settings_service()
|
||||
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
|
||||
return get_service(ServiceType.SETTINGS_SERVICE, SettingsServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_db_service() -> "DatabaseService":
|
||||
|
|
@ -116,7 +123,9 @@ def get_db_service() -> "DatabaseService":
|
|||
The DatabaseService instance.
|
||||
|
||||
"""
|
||||
return service_manager.get(ServiceType.DATABASE_SERVICE) # type: ignore
|
||||
from langflow.services.database.factory import DatabaseServiceFactory
|
||||
|
||||
return get_service(ServiceType.DATABASE_SERVICE, DatabaseServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_session() -> Generator["Session", None, None]:
|
||||
|
|
@ -165,7 +174,9 @@ def get_cache_service() -> "CacheService":
|
|||
Returns:
|
||||
The cache service instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.CACHE_SERVICE) # type: ignore
|
||||
from langflow.services.cache.factory import CacheServiceFactory
|
||||
|
||||
return get_service(ServiceType.CACHE_SERVICE, CacheServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_session_service() -> "SessionService":
|
||||
|
|
@ -175,7 +186,9 @@ def get_session_service() -> "SessionService":
|
|||
Returns:
|
||||
The session service instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.SESSION_SERVICE) # type: ignore
|
||||
from langflow.services.session.factory import SessionServiceFactory
|
||||
|
||||
return get_service(ServiceType.SESSION_SERVICE, SessionServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_monitor_service() -> "MonitorService":
|
||||
|
|
@ -185,7 +198,9 @@ def get_monitor_service() -> "MonitorService":
|
|||
Returns:
|
||||
MonitorService: The MonitorService instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.MONITOR_SERVICE) # type: ignore
|
||||
from langflow.services.monitor.factory import MonitorServiceFactory
|
||||
|
||||
return get_service(ServiceType.MONITOR_SERVICE, MonitorServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_task_service() -> "TaskService":
|
||||
|
|
@ -196,7 +211,9 @@ def get_task_service() -> "TaskService":
|
|||
The TaskService instance.
|
||||
|
||||
"""
|
||||
return service_manager.get(ServiceType.TASK_SERVICE) # type: ignore
|
||||
from langflow.services.task.factory import TaskServiceFactory
|
||||
|
||||
return get_service(ServiceType.TASK_SERVICE, TaskServiceFactory()) # type: ignore
|
||||
|
||||
|
||||
def get_chat_service() -> "ChatService":
|
||||
|
|
@ -206,7 +223,7 @@ def get_chat_service() -> "ChatService":
|
|||
Returns:
|
||||
ChatService: The chat service instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.CHAT_SERVICE) # type: ignore
|
||||
return get_service(ServiceType.CHAT_SERVICE) # type: ignore
|
||||
|
||||
|
||||
def get_store_service() -> "StoreService":
|
||||
|
|
@ -216,4 +233,4 @@ def get_store_service() -> "StoreService":
|
|||
Returns:
|
||||
StoreService: The StoreService instance.
|
||||
"""
|
||||
return service_manager.get(ServiceType.STORE_SERVICE) # type: ignore
|
||||
return get_service(ServiceType.STORE_SERVICE) # type: ignore
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
from typing import TYPE_CHECKING, Dict
|
||||
import importlib
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.base import Service
|
||||
|
||||
from langflow.services.factory import ServiceFactory
|
||||
from langflow.services.schema import ServiceType
|
||||
|
||||
|
||||
class NoFactoryRegisteredError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceManager:
|
||||
"""
|
||||
Manages the creation of different services.
|
||||
|
|
@ -16,6 +23,15 @@ class ServiceManager:
|
|||
def __init__(self):
|
||||
self.services: Dict[str, "Service"] = {}
|
||||
self.factories = {}
|
||||
self.register_factories()
|
||||
|
||||
def register_factories(self):
|
||||
for factory in self.get_factories():
|
||||
try:
|
||||
self.register_factory(factory)
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
logger.error(f"Error initializing {factory}: {exc}")
|
||||
|
||||
def register_factory(
|
||||
self,
|
||||
|
|
@ -28,24 +44,28 @@ class ServiceManager:
|
|||
service_name = service_factory.service_class.name
|
||||
self.factories[service_name] = service_factory
|
||||
|
||||
def get(self, service_name: "ServiceType") -> "Service":
|
||||
def get(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None) -> "Service":
|
||||
"""
|
||||
Get (or create) a service by its name.
|
||||
"""
|
||||
|
||||
if service_name not in self.services:
|
||||
self._create_service(service_name)
|
||||
self._create_service(service_name, default)
|
||||
|
||||
return self.services[service_name]
|
||||
|
||||
def _create_service(self, service_name: "ServiceType"):
|
||||
def _create_service(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None):
|
||||
"""
|
||||
Create a new service given its name, handling dependencies.
|
||||
"""
|
||||
logger.debug(f"Create service {service_name}")
|
||||
self._validate_service_creation(service_name)
|
||||
self._validate_service_creation(service_name, default)
|
||||
|
||||
# Create dependencies first
|
||||
factory = self.factories.get(service_name)
|
||||
if factory is None and default is not None:
|
||||
self.register_factory(default)
|
||||
factory = default
|
||||
for dependency in factory.dependencies:
|
||||
if dependency not in self.services:
|
||||
self._create_service(dependency)
|
||||
|
|
@ -57,12 +77,12 @@ class ServiceManager:
|
|||
self.services[service_name] = self.factories[service_name].create(**dependent_services)
|
||||
self.services[service_name].set_ready()
|
||||
|
||||
def _validate_service_creation(self, service_name: "ServiceType"):
|
||||
def _validate_service_creation(self, service_name: "ServiceType", default: Optional["ServiceFactory"] = None):
|
||||
"""
|
||||
Validate whether the service can be created.
|
||||
"""
|
||||
if service_name not in self.factories:
|
||||
raise ValueError(f"No factory registered for the service class '{service_name.name}'")
|
||||
if service_name not in self.factories and default is None:
|
||||
raise NoFactoryRegisteredError(f"No factory registered for the service class '{service_name.name}'")
|
||||
|
||||
def update(self, service_name: "ServiceType"):
|
||||
"""
|
||||
|
|
@ -88,6 +108,34 @@ class ServiceManager:
|
|||
self.services = {}
|
||||
self.factories = {}
|
||||
|
||||
@staticmethod
|
||||
def get_factories():
|
||||
from langflow.services.factory import ServiceFactory
|
||||
from langflow.services.schema import ServiceType
|
||||
|
||||
service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
|
||||
base_module = "langflow.services"
|
||||
factories = []
|
||||
|
||||
for name in service_names:
|
||||
try:
|
||||
module_name = f"{base_module}.{name}.factory"
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
# Find all classes in the module that are subclasses of ServiceFactory
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
|
||||
factories.append(obj())
|
||||
break
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
raise RuntimeError(
|
||||
f"Could not initialize services. Please check your settings. Error in {name}."
|
||||
) from exc
|
||||
|
||||
return factories
|
||||
|
||||
|
||||
service_manager = ServiceManager()
|
||||
|
||||
|
|
@ -106,9 +154,7 @@ def initialize_session_service():
|
|||
Initialize the session manager.
|
||||
"""
|
||||
from langflow.services.cache import factory as cache_factory
|
||||
from langflow.services.session import (
|
||||
factory as session_service_factory,
|
||||
) # type: ignore
|
||||
from langflow.services.session import factory as session_service_factory # type: ignore
|
||||
|
||||
initialize_settings_service()
|
||||
|
||||
|
|
|
|||
|
|
@ -19,5 +19,5 @@ class ServiceType(str, Enum):
|
|||
VARIABLE_SERVICE = "variable_service"
|
||||
STORAGE_SERVICE = "storage_service"
|
||||
MONITOR_SERVICE = "monitor_service"
|
||||
SOCKETIO_SERVICE = "socket_service"
|
||||
# SOCKETIO_SERVICE = "socket_service"
|
||||
STATE_SERVICE = "state_service"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
from typing import Coroutine, Optional
|
||||
|
||||
from langflow.interface.run import build_sorted_vertices
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.cache.base import CacheService
|
||||
from langflow.services.session.utils import compute_dict_hash, session_id_generator
|
||||
|
|
@ -25,8 +24,10 @@ class SessionService(Service):
|
|||
if data_graph is None:
|
||||
return (None, None)
|
||||
# If not cached, build the graph and cache it
|
||||
graph, artifacts = await build_sorted_vertices(data_graph, flow_id)
|
||||
from langflow.graph.graph.base import Graph
|
||||
|
||||
graph = Graph.from_payload(data_graph, flow_id=flow_id)
|
||||
artifacts: dict = {}
|
||||
await self.cache_service.set(key, (graph, artifacts))
|
||||
|
||||
return graph, artifacts
|
||||
|
|
|
|||
|
|
@ -100,9 +100,9 @@ class Settings(BaseSettings):
|
|||
|
||||
STORE: Optional[bool] = True
|
||||
STORE_URL: Optional[str] = "https://api.langflow.store"
|
||||
DOWNLOAD_WEBHOOK_URL: Optional[
|
||||
str
|
||||
] = "https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
|
||||
DOWNLOAD_WEBHOOK_URL: Optional[str] = (
|
||||
"https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
|
||||
)
|
||||
LIKE_WEBHOOK_URL: Optional[str] = "https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
|
||||
|
||||
STORAGE_TYPE: str = "local"
|
||||
|
|
|
|||
|
|
@ -1,41 +1,13 @@
|
|||
import importlib
|
||||
import inspect
|
||||
|
||||
from loguru import logger
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from langflow.services.auth.utils import create_super_user, verify_password
|
||||
from langflow.services.cache.factory import CacheServiceFactory
|
||||
from langflow.services.database.utils import initialize_database
|
||||
from langflow.services.factory import ServiceFactory
|
||||
from langflow.services.manager import service_manager
|
||||
from langflow.services.schema import ServiceType
|
||||
from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
|
||||
from langflow.services.socket.utils import set_socketio_server
|
||||
|
||||
from .deps import get_db_service, get_session, get_settings_service
|
||||
|
||||
|
||||
def get_factories():
|
||||
service_names = [ServiceType(service_type).value.replace("_service", "") for service_type in ServiceType]
|
||||
base_module = "langflow.services"
|
||||
factories = []
|
||||
|
||||
for name in service_names:
|
||||
try:
|
||||
module_name = f"{base_module}.{name}.factory"
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
# Find all classes in the module that are subclasses of ServiceFactory
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
if issubclass(obj, ServiceFactory) and obj is not ServiceFactory:
|
||||
factories.append(obj())
|
||||
break
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
raise RuntimeError(f"Could not initialize services. Please check your settings. Error in {name}.") from exc
|
||||
|
||||
return factories
|
||||
from .deps import get_db_service, get_service, get_session, get_settings_service
|
||||
|
||||
|
||||
def get_or_create_super_user(session: Session, username, password, is_default):
|
||||
|
|
@ -145,6 +117,8 @@ def teardown_services():
|
|||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
try:
|
||||
from langflow.services.manager import service_manager
|
||||
|
||||
service_manager.teardown()
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
|
|
@ -156,7 +130,7 @@ def initialize_settings_service():
|
|||
"""
|
||||
from langflow.services.settings import factory as settings_factory
|
||||
|
||||
service_manager.register_factory(settings_factory.SettingsServiceFactory())
|
||||
get_service(ServiceType.SETTINGS_SERVICE, settings_factory.SettingsServiceFactory())
|
||||
|
||||
|
||||
def initialize_session_service():
|
||||
|
|
@ -168,11 +142,13 @@ def initialize_session_service():
|
|||
|
||||
initialize_settings_service()
|
||||
|
||||
service_manager.register_factory(
|
||||
get_service(
|
||||
ServiceType.CACHE_SERVICE,
|
||||
cache_factory.CacheServiceFactory(),
|
||||
)
|
||||
|
||||
service_manager.register_factory(
|
||||
get_service(
|
||||
ServiceType.SESSION_SERVICE,
|
||||
session_service_factory.SessionServiceFactory(),
|
||||
)
|
||||
|
||||
|
|
@ -181,27 +157,17 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
|
|||
"""
|
||||
Initialize all the services needed.
|
||||
"""
|
||||
for factory in get_factories():
|
||||
try:
|
||||
service_manager.register_factory(factory)
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
logger.error(f"Error initializing {factory}: {exc}")
|
||||
|
||||
# Test cache connection
|
||||
service_manager.get(ServiceType.CACHE_SERVICE)
|
||||
get_service(ServiceType.CACHE_SERVICE, default=CacheServiceFactory())
|
||||
# Setup the superuser
|
||||
try:
|
||||
initialize_database(fix_migration=fix_migration)
|
||||
except Exception as exc:
|
||||
logger.error(exc)
|
||||
raise exc
|
||||
setup_superuser(service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session()))
|
||||
setup_superuser(get_service(ServiceType.SETTINGS_SERVICE), next(get_session()))
|
||||
try:
|
||||
get_db_service().migrate_flows_if_auto_login()
|
||||
except Exception as exc:
|
||||
logger.error(f"Error migrating flows: {exc}")
|
||||
raise RuntimeError("Error migrating flows") from exc
|
||||
|
||||
# Initialize the SocketIO service
|
||||
set_socketio_server(socketio_server)
|
||||
|
|
|
|||
|
|
@ -62,30 +62,6 @@ cryptography = "^42.0.5"
|
|||
asyncer = "^0.0.5"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest-asyncio = "^0.23.1"
|
||||
types-redis = "^4.6.0.5"
|
||||
ipykernel = "^6.26.0"
|
||||
mypy = "^1.7.1"
|
||||
ruff = "^0.3.5"
|
||||
httpx = "*"
|
||||
pytest = "^8.1.1"
|
||||
types-requests = "^2.31.0"
|
||||
requests = "^2.31.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pandas-stubs = "^2.2.1.230412"
|
||||
types-pillow = "^10.2.0.0"
|
||||
types-pyyaml = "^6.0.12.8"
|
||||
types-python-jose = "^3.3.4.8"
|
||||
types-passlib = "^1.7.7.13"
|
||||
locust = "^2.24.1"
|
||||
pytest-mock = "^3.14.0"
|
||||
pytest-xdist = "^3.5.0"
|
||||
types-pywin32 = "^306.0.0.4"
|
||||
types-google-cloud-ndb = "^2.3.0.0"
|
||||
pytest-sugar = "^1.0.0"
|
||||
|
||||
|
||||
[tool.poetry.extras]
|
||||
deploy = ["celery", "redis", "flower"]
|
||||
local = ["llama-cpp-python", "sentence-transformers", "ctransformers"]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import orjson
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from httpx import AsyncClient
|
||||
from sqlmodel import Session, SQLModel, create_engine, select
|
||||
from langflow.graph.graph.base import Graph
|
||||
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
|
||||
from langflow.services.auth.utils import get_password_hash
|
||||
|
|
@ -27,25 +28,39 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
def pytest_configure():
|
||||
pytest.BASIC_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "basic_example.json"
|
||||
pytest.COMPLEX_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_example.json"
|
||||
pytest.COMPLEX_DEPS_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_deps_example.json"
|
||||
pytest.OPENAPI_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "Openapi.json"
|
||||
pytest.GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "grouped_chat.json"
|
||||
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "one_group_chat.json"
|
||||
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "vector_store_grouped.json"
|
||||
data_path = Path(__file__).parent.absolute() / "data"
|
||||
|
||||
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = (
|
||||
Path(__file__).parent.absolute() / "data" / "BasicChatWithPromptAndHistory.json"
|
||||
)
|
||||
pytest.CHAT_INPUT = Path(__file__).parent.absolute() / "data" / "ChatInputTest.json"
|
||||
pytest.TWO_OUTPUTS = Path(__file__).parent.absolute() / "data" / "TwoOutputsTest.json"
|
||||
pytest.VECTOR_STORE_PATH = Path(__file__).parent.absolute() / "data" / "Vector_store.json"
|
||||
pytest.BASIC_EXAMPLE_PATH = data_path / "basic_example.json"
|
||||
pytest.COMPLEX_EXAMPLE_PATH = data_path / "complex_example.json"
|
||||
pytest.OPENAPI_EXAMPLE_PATH = data_path / "Openapi.json"
|
||||
pytest.GROUPED_CHAT_EXAMPLE_PATH = data_path / "grouped_chat.json"
|
||||
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = data_path / "one_group_chat.json"
|
||||
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = data_path / "vector_store_grouped.json"
|
||||
|
||||
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = data_path / "BasicChatwithPromptandHistory.json"
|
||||
pytest.CHAT_INPUT = data_path / "ChatInputTest.json"
|
||||
pytest.TWO_OUTPUTS = data_path / "TwoOutputsTest.json"
|
||||
pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json"
|
||||
pytest.CODE_WITH_SYNTAX_ERROR = """
|
||||
def get_text():
|
||||
retun "Hello World"
|
||||
"""
|
||||
|
||||
# validate that all the paths are correct and the files exist
|
||||
for path in [
|
||||
pytest.BASIC_EXAMPLE_PATH,
|
||||
pytest.COMPLEX_EXAMPLE_PATH,
|
||||
pytest.OPENAPI_EXAMPLE_PATH,
|
||||
pytest.GROUPED_CHAT_EXAMPLE_PATH,
|
||||
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH,
|
||||
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH,
|
||||
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY,
|
||||
pytest.CHAT_INPUT,
|
||||
pytest.TWO_OUTPUTS,
|
||||
pytest.VECTOR_STORE_PATH,
|
||||
]:
|
||||
assert path.exists(), f"File {path} does not exist. Available files: {list(data_path.iterdir())}"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_openai_api_key_in_environment_variables():
|
||||
|
|
@ -192,16 +207,6 @@ def json_vector_store():
|
|||
return f.read()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def complex_graph_with_groups():
|
||||
with open(pytest.COMPLEX_DEPS_EXAMPLE_PATH, "r") as f:
|
||||
flow_graph = json.load(f)
|
||||
data_graph = flow_graph["data"]
|
||||
nodes = data_graph["nodes"]
|
||||
edges = data_graph["edges"]
|
||||
return Graph(nodes, edges)
|
||||
|
||||
|
||||
@pytest.fixture(name="client", autouse=True)
|
||||
def client_fixture(session: Session, monkeypatch, request):
|
||||
# Set the database url to a test database
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
from io import StringIO
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from langflow.services.chat.cache import CacheService
|
||||
from PIL import Image
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cache_service():
|
||||
return CacheService()
|
||||
|
||||
|
||||
def test_cache_service_attach_detach_notify(cache_service):
|
||||
observer_called = False
|
||||
|
||||
def observer():
|
||||
nonlocal observer_called
|
||||
observer_called = True
|
||||
|
||||
cache_service.attach(observer)
|
||||
cache_service.notify()
|
||||
|
||||
assert observer_called
|
||||
|
||||
observer_called = False
|
||||
cache_service.detach(observer)
|
||||
cache_service.notify()
|
||||
|
||||
assert not observer_called
|
||||
|
||||
|
||||
def test_cache_service_client_context(cache_service):
|
||||
with cache_service.set_client_id("client1"):
|
||||
cache_service.add("foo", "bar", "string")
|
||||
assert cache_service.get("foo") == {
|
||||
"obj": "bar",
|
||||
"type": "string",
|
||||
"extension": "str",
|
||||
}
|
||||
|
||||
with cache_service.set_client_id("client2"):
|
||||
cache_service.add("baz", "qux", "string")
|
||||
assert cache_service.get("baz") == {
|
||||
"obj": "qux",
|
||||
"type": "string",
|
||||
"extension": "str",
|
||||
}
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
cache_service.get("foo")
|
||||
|
||||
|
||||
def test_cache_service_add_pandas(cache_service):
|
||||
df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]})
|
||||
|
||||
with cache_service.set_client_id("client1"):
|
||||
cache_service.add_pandas("test_df", df)
|
||||
cached_df = cache_service.get("test_df")
|
||||
assert cached_df["type"] == "pandas"
|
||||
assert cached_df["extension"] == "csv"
|
||||
read_df = pd.read_csv(StringIO(cached_df["obj"]), index_col=0)
|
||||
pd.testing.assert_frame_equal(df, read_df)
|
||||
|
||||
|
||||
def test_cache_service_add_image(cache_service):
|
||||
img = Image.new("RGB", (50, 50), color="red")
|
||||
|
||||
with cache_service.set_client_id("client1"):
|
||||
cache_service.add_image("test_image", img)
|
||||
cached_img = cache_service.get("test_image")
|
||||
assert cached_img["type"] == "image"
|
||||
assert cached_img["extension"] == "png"
|
||||
assert isinstance(cached_img["obj"], Image.Image)
|
||||
|
||||
|
||||
def test_cache_service_get_last(cache_service):
|
||||
with cache_service.set_client_id("client1"):
|
||||
cache_service.add("foo", "bar", "string")
|
||||
cache_service.add("baz", "qux", "string")
|
||||
last_item = cache_service.get_last()
|
||||
assert last_item == {"obj": "qux", "type": "string", "extension": "str"}
|
||||
|
|
@ -514,7 +514,7 @@ def test_successful_run_with_output_type_any(client, starter_project, created_ap
|
|||
|
||||
def test_successful_run_with_output_type_debug(client, starter_project, created_api_key):
|
||||
# This one should return outputs for all components
|
||||
# Let's just check the amount of outputs(there hsould be 7)
|
||||
# Let's just check the amount of outputs(there should be 7)
|
||||
headers = {"x-api-key": created_api_key.api_key}
|
||||
flow_id = starter_project["id"]
|
||||
payload = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue