Merge branch 'main' into dependabot/github_actions/install-pinned/ruff-b52a71f70b28264686d57d1efef1ba845b9cec6c

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-07-01 15:31:37 -03:00 committed by GitHub
commit 4a13afc13b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
231 changed files with 6516 additions and 3370 deletions

14
.github/workflows/auto-update.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Auto-update
on:
push:
branches:
- dev
- main
jobs:
Auto:
name: Auto-update
runs-on: ubuntu-latest
steps:
- uses: tibdex/auto-update@v2

View file

@ -11,7 +11,7 @@ on:
pre_release:
required: false
type: boolean
default: true
default: false
workflow_dispatch:
inputs:
@ -86,10 +86,10 @@ jobs:
include:
- component: backend
dockerfile: ./docker/build_and_push_backend.Dockerfile
tags: langflowai/langflow-backend:${{ inputs.version }},langflowai/langflow-backend:1.0-alpha
tags: ${{ inputs.pre_release == 'true' && format('langflowai/langflow-backend:{0}', inputs.version) || format('langflowai/langflow-backend:{0},langflowai/langflow-backend:latest', inputs.version) }}
- component: frontend
dockerfile: ./docker/frontend/build_and_push_frontend.Dockerfile
tags: langflowai/langflow-frontend:${{ inputs.version }},langflowai/langflow-frontend:1.0-alpha
tags: ${{ inputs.pre_release == 'true' && format('langflowai/langflow-frontend:{0}', inputs.version) || format('langflowai/langflow-frontend:{0},langflowai/langflow-frontend:latest', inputs.version) }}
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx

View file

@ -5,7 +5,7 @@ on:
paths:
- "src/frontend/**"
merge_group:
branches: [dev]
types: [checks_requested]
env:
NODE_VERSION: "21"
@ -45,10 +45,6 @@ jobs:
- name: Run Prettier
run: |
cd src/frontend
npm run format
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Apply Prettier formatting
branch: ${{ github.head_ref }}
npm run check-format

View file

@ -41,7 +41,7 @@ jobs:
poetry run python -m langflow run --host 127.0.0.1 --port 7860 --backend-only &
SERVER_PID=$!
# Wait for the server to start
timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/api/v1/auto_login; do sleep 5; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
# Terminate the server
kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
sleep 10 # give the server some time to terminate

View file

@ -58,7 +58,7 @@ jobs:
python -m langflow run --host 127.0.0.1 --port 7860 &
SERVER_PID=$!
# Wait for the server to start
timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/api/v1/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
# Terminate the server
kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
sleep 10 # give the server some time to terminate
@ -120,7 +120,7 @@ jobs:
python -m langflow run --host 127.0.0.1 --port 7860 &
SERVER_PID=$!
# Wait for the server to start
timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
timeout 120 bash -c 'until curl -f http://127.0.0.1:7860/api/v1/auto_login; do sleep 2; done' || (echo "Server did not start in time" && kill $SERVER_PID && exit 1)
# Terminate the server
kill $SERVER_PID || (echo "Failed to terminate the server" && exit 1)
sleep 10 # give the server some time to terminate

View file

@ -4,13 +4,14 @@ on:
workflow_dispatch:
inputs:
branch:
description: "Branch to run tests on"
required: true
description: "(Optional) Branch to checkout"
required: false
type: string
pull_request:
merge_group:
env:
POETRY_VERSION: "1.8.2"
POETRY_VERSION: "1.8.3"
NODE_VERSION: "21"
PYTHON_VERSION: "3.12"
# Define the directory where Playwright browsers will be installed.
@ -19,18 +20,23 @@ env:
jobs:
setup-and-test:
name: Run Frontend Tests on branch ${{ inputs.branch }}
name: Run Playwright Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shardTotal: [10]
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
STORE_API_KEY: ${{ secrets.STORE_API_KEY }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}
# If branch is passed as input, checkout that branch
# else checkout the default branch
ref: ${{ github.event.inputs.branch || github.ref }}
- name: Setup Node.js
uses: actions/setup-node@v4
@ -52,14 +58,15 @@ jobs:
cd src/frontend
npm ci
if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}
- name: Cache playwright binaries
uses: actions/cache@v4
- name: Get Playwright version
run: echo "PLAYWRIGHT_VERSION=$(jq '.devDependencies["@playwright/test"]' src/frontend/package.json -r)" >> $GITHUB_ENV
- name: Cache Playwright binaries
id: playwright-cache
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('src/frontend/package-lock.json') }}
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ runner.os }}-${{ env.PLAYWRIGHT_VERSION }}
- name: Install Frontend dependencies
run: |
cd src/frontend

View file

@ -47,20 +47,22 @@ init:
coverage: ## run the tests and generate a coverage report
poetry run pytest --cov \
--cov-config=.coveragerc \
--cov-report xml \
--cov-report term-missing:skip-covered \
--cov-report lcov:coverage/lcov-pytest.info
@poetry run coverage run
@poetry run coverage erase
# allow passing arguments to pytest
unit_tests:
poetry run pytest --ignore=tests/integration --instafail -ra -n auto -m "not api_key_required" $(args)
poetry run pytest \
--ignore=tests/integration \
--instafail -ra -n auto -m "not api_key_required" \
$(args)
integration_tests:
poetry run pytest tests/integration --instafail -ra -n auto $(args)
poetry run pytest tests/integration \
--instafail -ra -n auto \
$(args)
format: ## run code formatters
poetry run ruff check . --fix
@ -129,9 +131,20 @@ start:
@echo 'Running the CLI'
ifeq ($(open_browser),false)
@make install_backend && poetry run langflow run --path $(path) --log-level $(log_level) --host $(host) --port $(port) --env-file $(env) --no-open-browser
@make install_backend && poetry run langflow run \
--path $(path) \
--log-level $(log_level) \
--host $(host) \
--port $(port) \
--env-file $(env) \
--no-open-browser
else
@make install_backend && poetry run langflow run --path $(path) --log-level $(log_level) --host $(host) --port $(port) --env-file $(env)
@make install_backend && poetry run langflow run \
--path $(path) \
--log-level $(log_level) \
--host $(host) \
--port $(port) \
--env-file $(env)
endif
@ -166,13 +179,27 @@ backend: ## run the backend in development mode
@echo 'Setting up the environment'
@make setup_env
make install_backend
@-kill -9 $(lsof -t -i:7860)
@-kill -9 $$(lsof -t -i:7860)
ifdef login
@echo "Running backend autologin is $(login)";
LANGFLOW_AUTO_LOGIN=$(login) poetry run uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --env-file .env --loop asyncio --workers $(workers)
LANGFLOW_AUTO_LOGIN=$(login) poetry run uvicorn \
--factory langflow.main:create_app \
--host 0.0.0.0 \
--port $(port) \
--reload \
--env-file $(env) \
--loop asyncio \
--workers $(workers)
else
@echo "Running backend respecting the .env file";
poetry run uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --env-file .env --loop asyncio --workers $(workers)
@echo "Running backend respecting the $(env) file";
poetry run uvicorn \
--factory langflow.main:create_app \
--host 0.0.0.0 \
--port $(port) \
--reload \
--env-file $(env) \
--loop asyncio \
--workers $(workers)
endif

View file

@ -93,5 +93,7 @@ ENV PATH="/app/.venv/bin:${PATH}"
USER user
WORKDIR /app
ENTRYPOINT ["python", "-m", "langflow", "run"]
CMD ["--host", "0.0.0.0", "--port", "7860"]
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENTRYPOINT ["python", "-m", "langflow", "run"]

View file

@ -5,4 +5,4 @@ ARG LANGFLOW_IMAGE
FROM $LANGFLOW_IMAGE
RUN rm -rf /app/.venv/langflow/frontend
CMD ["--host", "0.0.0.0", "--port", "7860", "--backend-only"]
CMD ["--backend-only"]

View file

@ -95,6 +95,7 @@ USER user
# Install the package from the .tar.gz
RUN python -m pip install /app/src/backend/base/dist/*.tar.gz --user
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENTRYPOINT ["python", "-m", "langflow", "run"]
CMD ["--host", "0.0.0.0", "--port", "7860"]

View file

@ -1,15 +1,3 @@
FROM python:3.10-slim
FROM langflowai/langflow:latest
RUN apt-get update && apt-get install gcc g++ git make -y && apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -u 1000 user
USER user
ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH
WORKDIR $HOME/app
COPY --chown=user . $HOME/app
RUN pip install langflow>==0.5.0 -U --user
CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"]
ENTRYPOINT ["python", "-m", "langflow", "run"]

View file

@ -10,9 +10,9 @@ services:
environment:
- LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow
# This variable defines where the logs, file storage, monitor data and secret keys are stored.
- LANGFLOW_CONFIG_DIR=/var/lib/langflow
- LANGFLOW_CONFIG_DIR=/app/langflow
volumes:
- langflow-data:/var/lib/langflow
- langflow-data:/app/langflow
postgres:
image: postgres:16

281
poetry.lock generated
View file

@ -123,13 +123,13 @@ frozenlist = ">=1.1.0"
[[package]]
name = "alembic"
version = "1.13.1"
version = "1.13.2"
description = "A database migration tool for SQLAlchemy."
optional = false
python-versions = ">=3.8"
files = [
{file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"},
{file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"},
{file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"},
{file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"},
]
[package.dependencies]
@ -167,13 +167,13 @@ files = [
[[package]]
name = "anthropic"
version = "0.29.0"
version = "0.30.0"
description = "The official Python library for the anthropic API"
optional = false
python-versions = ">=3.7"
files = [
{file = "anthropic-0.29.0-py3-none-any.whl", hash = "sha256:d16010715129c8bc3295b74fbf4da73cfb156618bf0abb2d007255983266b76a"},
{file = "anthropic-0.29.0.tar.gz", hash = "sha256:3eb558a232d83bdf7cdedb75663bf7ff7a8b50cc10acaa9ce6494ff295b8506a"},
{file = "anthropic-0.30.0-py3-none-any.whl", hash = "sha256:061bf58c9c64968361e6c21c76ff5016a6f7fdd9a5f6b7f2280ede2c3b44bfd5"},
{file = "anthropic-0.30.0.tar.gz", hash = "sha256:9e9ee2bfce833370eac74d7de433db97a0bf141f9118c40ac0e2f4c39bc2b76f"},
]
[package.dependencies]
@ -461,17 +461,17 @@ files = [
[[package]]
name = "boto3"
version = "1.34.132"
version = "1.34.133"
description = "The AWS SDK for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "boto3-1.34.132-py3-none-any.whl", hash = "sha256:b5d1681a0d8bf255787c8b37f911d706672d5722c9ace5342cd283a3cdb04820"},
{file = "boto3-1.34.132.tar.gz", hash = "sha256:3b2964060620f1bbe9574b5f8d3fb2a4e087faacfc6023c24154b184f1b16443"},
{file = "boto3-1.34.133-py3-none-any.whl", hash = "sha256:da7e78c03270be872ad78301892396ffea56647efcb2c3a8621ef46a905541ab"},
{file = "boto3-1.34.133.tar.gz", hash = "sha256:7071f8ce1f09113ca5630860fd590464e6325a4df55faae83c956225941016fc"},
]
[package.dependencies]
botocore = ">=1.34.132,<1.35.0"
botocore = ">=1.34.133,<1.35.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0"
@ -480,13 +480,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.34.132"
version = "1.34.133"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.8"
files = [
{file = "botocore-1.34.132-py3-none-any.whl", hash = "sha256:06ef8b4bd3b3cb5a9b9a4273a543b257be3304030978ba51516b576a65156c39"},
{file = "botocore-1.34.132.tar.gz", hash = "sha256:372a6cfce29e5de9bcf8c95af901d0bc3e27d8aa2295fadee295424f95f43f16"},
{file = "botocore-1.34.133-py3-none-any.whl", hash = "sha256:f269dad8e17432d2527b97ed9f1fd30ec8dc705f8b818957170d1af484680ef2"},
{file = "botocore-1.34.133.tar.gz", hash = "sha256:5ea609aa4831a6589e32eef052a359ad8d7311733b4d86a9d35dab4bd3ec80ff"},
]
[package.dependencies]
@ -1501,33 +1501,33 @@ vision = ["Pillow (>=6.2.1)"]
[[package]]
name = "debugpy"
version = "1.8.1"
version = "1.8.2"
description = "An implementation of the Debug Adapter Protocol for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "debugpy-1.8.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741"},
{file = "debugpy-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e"},
{file = "debugpy-1.8.1-cp310-cp310-win32.whl", hash = "sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0"},
{file = "debugpy-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd"},
{file = "debugpy-1.8.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb"},
{file = "debugpy-1.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"},
{file = "debugpy-1.8.1-cp311-cp311-win32.whl", hash = "sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146"},
{file = "debugpy-1.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8"},
{file = "debugpy-1.8.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539"},
{file = "debugpy-1.8.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace"},
{file = "debugpy-1.8.1-cp312-cp312-win32.whl", hash = "sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0"},
{file = "debugpy-1.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98"},
{file = "debugpy-1.8.1-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39"},
{file = "debugpy-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7"},
{file = "debugpy-1.8.1-cp38-cp38-win32.whl", hash = "sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9"},
{file = "debugpy-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234"},
{file = "debugpy-1.8.1-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42"},
{file = "debugpy-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703"},
{file = "debugpy-1.8.1-cp39-cp39-win32.whl", hash = "sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23"},
{file = "debugpy-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3"},
{file = "debugpy-1.8.1-py2.py3-none-any.whl", hash = "sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242"},
{file = "debugpy-1.8.1.zip", hash = "sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42"},
{file = "debugpy-1.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2"},
{file = "debugpy-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6"},
{file = "debugpy-1.8.2-cp310-cp310-win32.whl", hash = "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47"},
{file = "debugpy-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3"},
{file = "debugpy-1.8.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a"},
{file = "debugpy-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634"},
{file = "debugpy-1.8.2-cp311-cp311-win32.whl", hash = "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad"},
{file = "debugpy-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa"},
{file = "debugpy-1.8.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835"},
{file = "debugpy-1.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3"},
{file = "debugpy-1.8.2-cp312-cp312-win32.whl", hash = "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e"},
{file = "debugpy-1.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859"},
{file = "debugpy-1.8.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d"},
{file = "debugpy-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02"},
{file = "debugpy-1.8.2-cp38-cp38-win32.whl", hash = "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031"},
{file = "debugpy-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210"},
{file = "debugpy-1.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9"},
{file = "debugpy-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1"},
{file = "debugpy-1.8.2-cp39-cp39-win32.whl", hash = "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326"},
{file = "debugpy-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755"},
{file = "debugpy-1.8.2-py2.py3-none-any.whl", hash = "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca"},
{file = "debugpy-1.8.2.zip", hash = "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00"},
]
[[package]]
@ -4122,19 +4122,19 @@ adal = ["adal (>=1.0.2)"]
[[package]]
name = "langchain"
version = "0.2.5"
version = "0.2.6"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain-0.2.5-py3-none-any.whl", hash = "sha256:9aded9a65348254e1c93dcdaacffe4d1b6a5e7f74ef80c160c88ff78ad299228"},
{file = "langchain-0.2.5.tar.gz", hash = "sha256:ffdbf4fcea46a10d461bcbda2402220fcfd72a0c70e9f4161ae0510067b9b3bd"},
{file = "langchain-0.2.6-py3-none-any.whl", hash = "sha256:f86e8a7afd3e56f8eb5ba47f01dd00144fb9fc2f1db9873bd197347be2857aa4"},
{file = "langchain-0.2.6.tar.gz", hash = "sha256:867f6add370c1e3911b0e87d3dd0e36aec1e8f513bf06131340fe8f151d89dc5"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
langchain-core = ">=0.2.7,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
langchain-text-splitters = ">=0.2.0,<0.3.0"
langsmith = ">=0.1.17,<0.2.0"
numpy = [
@ -4145,7 +4145,7 @@ pydantic = ">=1,<3"
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-anthropic"
@ -4181,18 +4181,18 @@ numpy = ">=1,<2"
[[package]]
name = "langchain-aws"
version = "0.1.7"
version = "0.1.8"
description = "An integration package connecting AWS and LangChain"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_aws-0.1.7-py3-none-any.whl", hash = "sha256:413f88cbb120cc1d6ca0e9f6d72b89c1d930b78ce071fef5b03e1595fc4d6029"},
{file = "langchain_aws-0.1.7.tar.gz", hash = "sha256:aa0bbd3e530e21fdc1d0459e97ee14fa387ce9bb2d00d721cf526e9c3ecea78f"},
{file = "langchain_aws-0.1.8-py3-none-any.whl", hash = "sha256:d1ade6d01af7d86f42c106bb32c08a99a38c84f54a3d669201362f42fd2684b8"},
{file = "langchain_aws-0.1.8.tar.gz", hash = "sha256:d1e5edbda092ddbeda45ef8245a494b5b4f6bef79ed5afd56054c7d348dfed74"},
]
[package.dependencies]
boto3 = ">=1.34.51,<1.35.0"
langchain-core = ">=0.2.2,<0.3"
boto3 = ">=1.34.127,<1.35.0"
langchain-core = ">=0.2.6,<0.3"
numpy = ">=1,<2"
[[package]]
@ -4229,20 +4229,20 @@ langchain-core = ">=0.2.0,<0.3"
[[package]]
name = "langchain-community"
version = "0.2.5"
version = "0.2.6"
description = "Community contributed LangChain integrations."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_community-0.2.5-py3-none-any.whl", hash = "sha256:bf37a334952e42c7676d083cf2d2c4cbfbb7de1949c4149fe19913e2b06c485f"},
{file = "langchain_community-0.2.5.tar.gz", hash = "sha256:476787b8c8c213b67e7b0eceb53346e787f00fbae12d8e680985bd4f93b0bf64"},
{file = "langchain_community-0.2.6-py3-none-any.whl", hash = "sha256:758cc800acfe5dd396bf8ba1b57c4792639ead0eab48ed0367f0732ec6ee1f68"},
{file = "langchain_community-0.2.6.tar.gz", hash = "sha256:40ce09a50ed798aa651ddb34c8978200fa8589b9813c7a28ce8af027bbf249f0"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
dataclasses-json = ">=0.5.7,<0.7"
langchain = ">=0.2.5,<0.3.0"
langchain-core = ">=0.2.7,<0.3.0"
langchain = ">=0.2.6,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
langsmith = ">=0.1.0,<0.2.0"
numpy = [
{version = ">=1,<2", markers = "python_version < \"3.12\""},
@ -4251,17 +4251,17 @@ numpy = [
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-core"
version = "0.2.9"
version = "0.2.10"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_core-0.2.9-py3-none-any.whl", hash = "sha256:426a5a4fea95a5db995ba5ab560b76edd4998fb6fe52ccc28ac987092a4cbfcd"},
{file = "langchain_core-0.2.9.tar.gz", hash = "sha256:f1c59082642921727844e1cd0eb36d451edd1872c20e193aa3142aac03495986"},
{file = "langchain_core-0.2.10-py3-none-any.whl", hash = "sha256:6eb72086b6bc86db9812da98f79e507c2209a15c0112aefd214a04182ada8586"},
{file = "langchain_core-0.2.10.tar.gz", hash = "sha256:33d1fc234ab58c80476eb5bbde2107ef522a2ce8f46bdf47d9e1bd21e054208f"},
]
[package.dependencies]
@ -4277,18 +4277,18 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-experimental"
version = "0.0.61"
version = "0.0.62"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_experimental-0.0.61-py3-none-any.whl", hash = "sha256:f9c516f528f55919743bd56fe1689a53bf74ae7f8902d64b9d8aebc61249cbe2"},
{file = "langchain_experimental-0.0.61.tar.gz", hash = "sha256:e9538efb994be5db3045cc582cddb9787c8299c86ffeee9d3779b7f58eef2226"},
{file = "langchain_experimental-0.0.62-py3-none-any.whl", hash = "sha256:9240f9e3490e819976f20a37863970036e7baacb7104b9eb6833d19ab6d518c9"},
{file = "langchain_experimental-0.0.62.tar.gz", hash = "sha256:9737fbc8429d24457ea4d368e3c9ba9ed1cace0564fb5f1a96a3027a588bd0ac"},
]
[package.dependencies]
langchain-community = ">=0.2.5,<0.3.0"
langchain-core = ">=0.2.7,<0.3.0"
langchain-community = ">=0.2.6,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
[[package]]
name = "langchain-google-genai"
@ -4377,13 +4377,13 @@ pymongo = ">=4.6.1,<5.0"
[[package]]
name = "langchain-openai"
version = "0.1.9"
version = "0.1.10"
description = "An integration package connecting OpenAI and LangChain"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_openai-0.1.9-py3-none-any.whl", hash = "sha256:afae71ef315c967685e53fe061470438000946739a9492d5f2d53bd4ae9d495a"},
{file = "langchain_openai-0.1.9.tar.gz", hash = "sha256:730a94d68208678b9b9f64e4959a87057e021d6600754ea8b954e7765c7a62f1"},
{file = "langchain_openai-0.1.10-py3-none-any.whl", hash = "sha256:62eb000980eb45e4f16c88acdbaeccf3d59266554b0dd3ce6bebea1bbe8143dd"},
{file = "langchain_openai-0.1.10.tar.gz", hash = "sha256:30f881f8ccaec28c054759837c41fd2a2264fcc5564728ce12e1715891a9ce3c"},
]
[package.dependencies]
@ -4409,20 +4409,17 @@ pinecone-client = ">=3.2.2,<4.0.0"
[[package]]
name = "langchain-text-splitters"
version = "0.2.1"
version = "0.2.2"
description = "LangChain text splitting utilities"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_text_splitters-0.2.1-py3-none-any.whl", hash = "sha256:c2774a85f17189eaca50339629d2316d13130d4a8d9f1a1a96f3a03670c4a138"},
{file = "langchain_text_splitters-0.2.1.tar.gz", hash = "sha256:06853d17d7241ecf5c97c7b6ef01f600f9b0fb953dd997838142a527a4f32ea4"},
{file = "langchain_text_splitters-0.2.2-py3-none-any.whl", hash = "sha256:1c80d4b11b55e2995f02d2a326c0323ee1eeff24507329bb22924e420c782dff"},
{file = "langchain_text_splitters-0.2.2.tar.gz", hash = "sha256:a1e45de10919fa6fb080ef0525deab56557e9552083600455cb9fa4238076140"},
]
[package.dependencies]
langchain-core = ">=0.2.0,<0.3.0"
[package.extras]
extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
langchain-core = ">=0.2.10,<0.3.0"
[[package]]
name = "langchainhub"
@ -4456,7 +4453,7 @@ six = "*"
[[package]]
name = "langflow-base"
version = "0.0.80"
version = "0.0.81"
description = "A Python package with a built-in web application"
optional = false
python-versions = ">=3.10,<3.13"
@ -4474,6 +4471,7 @@ docstring-parser = "^0.15"
duckdb = "^1.0.0"
emoji = "^2.12.0"
fastapi = "^0.111.0"
firecrawl-py = "^0.0.16"
gunicorn = "^22.0.0"
httpx = "*"
jq = {version = "^1.7.0", markers = "sys_platform != \"win32\""}
@ -4557,13 +4555,13 @@ requests = ">=2,<3"
[[package]]
name = "litellm"
version = "1.40.26"
version = "1.40.27"
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.40.26-py3-none-any.whl", hash = "sha256:5daedec00a3a8e32f55b3099190ccd9d550177f6e516823002831e6620ae771c"},
{file = "litellm-1.40.26.tar.gz", hash = "sha256:4dfd4ca3eb50a62600e60303c4975ba9fe7c52d07882d0d2b6bad2d474d88758"},
{file = "litellm-1.40.27-py3-none-any.whl", hash = "sha256:f6906e5260d784e7e31d579f5b28545e87517268cb96dd0dcaf31e4c5d34073f"},
{file = "litellm-1.40.27.tar.gz", hash = "sha256:a13a04168be5a8e52d43c34c2e657ca2521da61039ac39a17abc233a1875923f"},
]
[package.dependencies]
@ -5748,13 +5746,13 @@ sympy = "*"
[[package]]
name = "openai"
version = "1.35.3"
version = "1.35.5"
description = "The official Python library for the openai API"
optional = false
python-versions = ">=3.7.1"
files = [
{file = "openai-1.35.3-py3-none-any.whl", hash = "sha256:7b26544cef80f125431c073ffab3811d2421fbb9e30d3bd5c2436aba00b042d5"},
{file = "openai-1.35.3.tar.gz", hash = "sha256:d6177087f150b381d49499be782d764213fdf638d391b29ca692b84dd675a389"},
{file = "openai-1.35.5-py3-none-any.whl", hash = "sha256:28d92503c6e4b6a32a89277b36693023ef41f60922a4b5c8c621e8c5697ae3a6"},
{file = "openai-1.35.5.tar.gz", hash = "sha256:67ef289ae22d350cbf9381d83ae82c4e3596d71b7ad1cc886143554ee12fe0c9"},
]
[package.dependencies]
@ -7067,71 +7065,61 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pymongo"
version = "4.7.3"
version = "4.8.0"
description = "Python driver for MongoDB <http://www.mongodb.org>"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pymongo-4.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e9580b4537b3cc5d412070caabd1dabdf73fdce249793598792bac5782ecf2eb"},
{file = "pymongo-4.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:517243b2b189c98004570dd8fc0e89b1a48363d5578b3b99212fa2098b2ea4b8"},
{file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23b1e9dabd61da1c7deb54d888f952f030e9e35046cebe89309b28223345b3d9"},
{file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03e0f9901ad66c6fb7da0d303461377524d61dab93a4e4e5af44164c5bb4db76"},
{file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a870824aa54453aee030bac08c77ebcf2fe8999400f0c2a065bebcbcd46b7f8"},
{file = "pymongo-4.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd7b3d3f4261bddbb74a332d87581bc523353e62bb9da4027cc7340f6fcbebc"},
{file = "pymongo-4.7.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d719a643ea6da46d215a3ba51dac805a773b611c641319558d8576cbe31cef8"},
{file = "pymongo-4.7.3-cp310-cp310-win32.whl", hash = "sha256:d8b1e06f361f3c66ee694cb44326e1a2e4f93bc9c3a4849ae8547889fca71154"},
{file = "pymongo-4.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:c450ab2f9397e2d5caa7fddeb4feb30bf719c47c13ae02c0bbb3b71bf4099c1c"},
{file = "pymongo-4.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cc6459209e885ba097779eaa0fe7f2fa049db39ab43b1731cf8d065a4650e8"},
{file = "pymongo-4.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6e2287f1e2cc35e73cd74a4867e398a97962c5578a3991c730ef78d276ca8e46"},
{file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:413506bd48d8c31ee100645192171e4773550d7cb940b594d5175ac29e329ea1"},
{file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cc1febf17646d52b7561caa762f60bdfe2cbdf3f3e70772f62eb624269f9c05"},
{file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8dfcf18a49955d50a16c92b39230bd0668ffc9c164ccdfe9d28805182b48fa72"},
{file = "pymongo-4.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89872041196c008caddf905eb59d3dc2d292ae6b0282f1138418e76f3abd3ad6"},
{file = "pymongo-4.7.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3ed97b89de62ea927b672ad524de0d23f3a6b4a01c8d10e3d224abec973fbc3"},
{file = "pymongo-4.7.3-cp311-cp311-win32.whl", hash = "sha256:d2f52b38151e946011d888a8441d3d75715c663fc5b41a7ade595e924e12a90a"},
{file = "pymongo-4.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:4a4cc91c28e81c0ce03d3c278e399311b0af44665668a91828aec16527082676"},
{file = "pymongo-4.7.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cb30c8a78f5ebaca98640943447b6a0afcb146f40b415757c9047bf4a40d07b4"},
{file = "pymongo-4.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9cf2069f5d37c398186453589486ea98bb0312214c439f7d320593b61880dc05"},
{file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3564f423958fced8a8c90940fd2f543c27adbcd6c7c6ed6715d847053f6200a0"},
{file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a8af8a38fa6951fff73e6ff955a6188f829b29fed7c5a1b739a306b4aa56fe8"},
{file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a0e81c8dba6d825272867d487f18764cfed3c736d71d7d4ff5b79642acbed42"},
{file = "pymongo-4.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88fc1d146feabac4385ea8ddb1323e584922922641303c8bf392fe1c36803463"},
{file = "pymongo-4.7.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4225100b2c5d1f7393d7c5d256ceb8b20766830eecf869f8ae232776347625a6"},
{file = "pymongo-4.7.3-cp312-cp312-win32.whl", hash = "sha256:5f3569ed119bf99c0f39ac9962fb5591eff02ca210fe80bb5178d7a1171c1b1e"},
{file = "pymongo-4.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:eb383c54c0c8ba27e7712b954fcf2a0905fee82a929d277e2e94ad3a5ba3c7db"},
{file = "pymongo-4.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a46cffe91912570151617d866a25d07b9539433a32231ca7e7cf809b6ba1745f"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c3cba427dac50944c050c96d958c5e643c33a457acee03bae27c8990c5b9c16"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7a5fd893edbeb7fa982f8d44b6dd0186b6cd86c89e23f6ef95049ff72bffe46"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c168a2fadc8b19071d0a9a4f85fe38f3029fe22163db04b4d5c046041c0b14bd"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c59c2c9e70f63a7f18a31e367898248c39c068c639b0579623776f637e8f482"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08165fd82c89d372e82904c3268bd8fe5de44f92a00e97bb1db1785154397d9"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:397fed21afec4fdaecf72f9c4344b692e489756030a9c6d864393e00c7e80491"},
{file = "pymongo-4.7.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f903075f8625e2d228f1b9b9a0cf1385f1c41e93c03fd7536c91780a0fb2e98f"},
{file = "pymongo-4.7.3-cp37-cp37m-win32.whl", hash = "sha256:8ed1132f58c38add6b6138b771d0477a3833023c015c455d9a6e26f367f9eb5c"},
{file = "pymongo-4.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8d00a5d8fc1043a4f641cbb321da766699393f1b6f87c70fae8089d61c9c9c54"},
{file = "pymongo-4.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9377b868c38700c7557aac1bc4baae29f47f1d279cc76b60436e547fd643318c"},
{file = "pymongo-4.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da4a6a7b4f45329bb135aa5096823637bd5f760b44d6224f98190ee367b6b5dd"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:487e2f9277f8a63ac89335ec4f1699ae0d96ebd06d239480d69ed25473a71b2c"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db3d608d541a444c84f0bfc7bad80b0b897e0f4afa580a53f9a944065d9b633"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e90af2ad3a8a7c295f4d09a2fbcb9a350c76d6865f787c07fe843b79c6e821d1"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e28feb18dc559d50ededba27f9054c79f80c4edd70a826cecfe68f3266807b3"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f21ecddcba2d9132d5aebd8e959de8d318c29892d0718420447baf2b9bccbb19"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:26140fbb3f6a9a74bd73ed46d0b1f43d5702e87a6e453a31b24fad9c19df9358"},
{file = "pymongo-4.7.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:94baa5fc7f7d22c3ce2ac7bd92f7e03ba7a6875f2480e3b97a400163d6eaafc9"},
{file = "pymongo-4.7.3-cp38-cp38-win32.whl", hash = "sha256:92dd247727dd83d1903e495acc743ebd757f030177df289e3ba4ef8a8c561fad"},
{file = "pymongo-4.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:1c90c848a5e45475731c35097f43026b88ef14a771dfd08f20b67adc160a3f79"},
{file = "pymongo-4.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f598be401b416319a535c386ac84f51df38663f7a9d1071922bda4d491564422"},
{file = "pymongo-4.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35ba90477fae61c65def6e7d09e8040edfdd3b7fd47c3c258b4edded60c4d625"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aa8735955c70892634d7e61b0ede9b1eefffd3cd09ccabee0ffcf1bdfe62254"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:82a97d8f7f138586d9d0a0cff804a045cdbbfcfc1cd6bba542b151e284fbbec5"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de3b9db558930efab5eaef4db46dcad8bf61ac3ddfd5751b3e5ac6084a25e366"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0e149217ef62812d3c2401cf0e2852b0c57fd155297ecc4dcd67172c4eca402"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3a8a1ef4a824f5feb793b3231526d0045eadb5eb01080e38435dfc40a26c3e5"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d14e5e89a4be1f10efc3d9dcb13eb7a3b2334599cb6bb5d06c6a9281b79c8e22"},
{file = "pymongo-4.7.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6bfa29f032fd4fd7b129520f8cdb51ab71d88c2ba0567cccd05d325f963acb5"},
{file = "pymongo-4.7.3-cp39-cp39-win32.whl", hash = "sha256:1421d0bd2ce629405f5157bd1aaa9b83f12d53a207cf68a43334f4e4ee312b66"},
{file = "pymongo-4.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:f7ee974f8b9370a998919c55b1050889f43815ab588890212023fecbc0402a6d"},
{file = "pymongo-4.7.3.tar.gz", hash = "sha256:6354a66b228f2cd399be7429685fb68e07f19110a3679782ecb4fdb68da03831"},
{file = "pymongo-4.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2b7bec27e047e84947fbd41c782f07c54c30c76d14f3b8bf0c89f7413fac67a"},
{file = "pymongo-4.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c68fe128a171493018ca5c8020fc08675be130d012b7ab3efe9e22698c612a1"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920d4f8f157a71b3cb3f39bc09ce070693d6e9648fb0e30d00e2657d1dca4e49"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b4108ac9469febba18cea50db972605cc43978bedaa9fea413378877560ef8"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:180d5eb1dc28b62853e2f88017775c4500b07548ed28c0bd9c005c3d7bc52526"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aec2b9088cdbceb87e6ca9c639d0ff9b9d083594dda5ca5d3c4f6774f4c81b33"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0cf61450feadca81deb1a1489cb1a3ae1e4266efd51adafecec0e503a8dcd84"},
{file = "pymongo-4.8.0-cp310-cp310-win32.whl", hash = "sha256:8b18c8324809539c79bd6544d00e0607e98ff833ca21953df001510ca25915d1"},
{file = "pymongo-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e5df28f74002e37bcbdfdc5109799f670e4dfef0fb527c391ff84f078050e7b5"},
{file = "pymongo-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b50040d9767197b77ed420ada29b3bf18a638f9552d80f2da817b7c4a4c9c68"},
{file = "pymongo-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:417369ce39af2b7c2a9c7152c1ed2393edfd1cbaf2a356ba31eb8bcbd5c98dd7"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf821bd3befb993a6db17229a2c60c1550e957de02a6ff4dd0af9476637b2e4d"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9365166aa801c63dff1a3cb96e650be270da06e3464ab106727223123405510f"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc8b8582f4209c2459b04b049ac03c72c618e011d3caa5391ff86d1bda0cc486"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e5019f75f6827bb5354b6fef8dfc9d6c7446894a27346e03134d290eb9e758"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b5802151fc2b51cd45492c80ed22b441d20090fb76d1fd53cd7760b340ff554"},
{file = "pymongo-4.8.0-cp311-cp311-win32.whl", hash = "sha256:4bf58e6825b93da63e499d1a58de7de563c31e575908d4e24876234ccb910eba"},
{file = "pymongo-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b747c0e257b9d3e6495a018309b9e0c93b7f0d65271d1d62e572747f4ffafc88"},
{file = "pymongo-4.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e6a720a3d22b54183352dc65f08cd1547204d263e0651b213a0a2e577e838526"},
{file = "pymongo-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31e4d21201bdf15064cf47ce7b74722d3e1aea2597c6785882244a3bb58c7eab"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b804bb4f2d9dc389cc9e827d579fa327272cdb0629a99bfe5b83cb3e269ebf"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fbdb87fe5075c8beb17a5c16348a1ea3c8b282a5cb72d173330be2fecf22f5"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd39455b7ee70aabee46f7399b32ab38b86b236c069ae559e22be6b46b2bbfc4"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940d456774b17814bac5ea7fc28188c7a1338d4a233efbb6ba01de957bded2e8"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:236bbd7d0aef62e64caf4b24ca200f8c8670d1a6f5ea828c39eccdae423bc2b2"},
{file = "pymongo-4.8.0-cp312-cp312-win32.whl", hash = "sha256:47ec8c3f0a7b2212dbc9be08d3bf17bc89abd211901093e3ef3f2adea7de7a69"},
{file = "pymongo-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84bc7707492f06fbc37a9f215374d2977d21b72e10a67f1b31893ec5a140ad8"},
{file = "pymongo-4.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:519d1bab2b5e5218c64340b57d555d89c3f6c9d717cecbf826fb9d42415e7750"},
{file = "pymongo-4.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87075a1feb1e602e539bdb1ef8f4324a3427eb0d64208c3182e677d2c0718b6f"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f53429515d2b3e86dcc83dadecf7ff881e538c168d575f3688698a8707b80a"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdc20cd1e1141b04696ffcdb7c71e8a4a665db31fe72e51ec706b3bdd2d09f36"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:284d0717d1a7707744018b0b6ee7801b1b1ff044c42f7be7a01bb013de639470"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5bf0eb8b6ef40fa22479f09375468c33bebb7fe49d14d9c96c8fd50355188b0"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ecd71b9226bd1d49416dc9f999772038e56f415a713be51bf18d8676a0841c8"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0061af6e8c5e68b13f1ec9ad5251247726653c5af3c0bbdfbca6cf931e99216"},
{file = "pymongo-4.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:658d0170f27984e0d89c09fe5c42296613b711a3ffd847eb373b0dbb5b648d5f"},
{file = "pymongo-4.8.0-cp38-cp38-win32.whl", hash = "sha256:3ed1c316718a2836f7efc3d75b4b0ffdd47894090bc697de8385acd13c513a70"},
{file = "pymongo-4.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:7148419eedfea9ecb940961cfe465efaba90595568a1fb97585fb535ea63fe2b"},
{file = "pymongo-4.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8400587d594761e5136a3423111f499574be5fd53cf0aefa0d0f05b180710b0"},
{file = "pymongo-4.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af3e98dd9702b73e4e6fd780f6925352237f5dce8d99405ff1543f3771201704"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de3a860f037bb51f968de320baef85090ff0bbb42ec4f28ec6a5ddf88be61871"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fc18b3a093f3db008c5fea0e980dbd3b743449eee29b5718bc2dc15ab5088bb"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18c9d8f975dd7194c37193583fd7d1eb9aea0c21ee58955ecf35362239ff31ac"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:408b2f8fdbeca3c19e4156f28fff1ab11c3efb0407b60687162d49f68075e63c"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6564780cafd6abeea49759fe661792bd5a67e4f51bca62b88faab497ab5fe89"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d18d86bc9e103f4d3d4f18b85a0471c0e13ce5b79194e4a0389a224bb70edd53"},
{file = "pymongo-4.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9097c331577cecf8034422956daaba7ec74c26f7b255d718c584faddd7fa2e3c"},
{file = "pymongo-4.8.0-cp39-cp39-win32.whl", hash = "sha256:d5428dbcd43d02f6306e1c3c95f692f68b284e6ee5390292242f509004c9e3a8"},
{file = "pymongo-4.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:ef7225755ed27bfdb18730c68f6cb023d06c28f2b734597480fb4c0e500feb6f"},
{file = "pymongo-4.8.0.tar.gz", hash = "sha256:454f2295875744dc70f1881e4b2eb99cdad008a33574bc8aaf120530f66c0cde"},
]
[package.dependencies]
@ -7139,6 +7127,7 @@ dnspython = ">=1.16.0,<3.0.0"
[package.extras]
aws = ["pymongo-auth-aws (>=1.1.0,<2.0.0)"]
docs = ["furo (==2023.9.10)", "readthedocs-sphinx-search (>=0.3,<1.0)", "sphinx (>=5.3,<8)", "sphinx-rtd-theme (>=2,<3)", "sphinxcontrib-shellcheck (>=1,<2)"]
encryption = ["certifi", "pymongo-auth-aws (>=1.1.0,<2.0.0)", "pymongocrypt (>=1.6.0,<2.0.0)"]
gssapi = ["pykerberos", "winkerberos (>=0.5.0)"]
ocsp = ["certifi", "cryptography (>=2.5)", "pyopenssl (>=17.2.0)", "requests (<3.0.0)", "service-identity (>=18.1.0)"]
@ -7880,13 +7869,13 @@ websockets = ">=11,<13"
[[package]]
name = "redis"
version = "5.0.6"
version = "5.0.7"
description = "Python client for Redis database and key-value store"
optional = false
python-versions = ">=3.7"
files = [
{file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"},
{file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"},
{file = "redis-5.0.7-py3-none-any.whl", hash = "sha256:0e479e24da960c690be5d9b96d21f7b918a98c0cf49af3b6fafaa0753f93a0db"},
{file = "redis-5.0.7.tar.gz", hash = "sha256:8f611490b93c8109b50adc317b31bfd84fff31def3475b92e7e80bf39f48175b"},
]
[package.dependencies]
@ -8361,13 +8350,13 @@ dev = ["pre-commit", "pytest", "ruff (>=0.3.0)"]
[[package]]
name = "sentry-sdk"
version = "2.6.0"
version = "2.7.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
files = [
{file = "sentry_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:422b91cb49378b97e7e8d0e8d5a1069df23689d45262b86f54988a7db264e874"},
{file = "sentry_sdk-2.6.0.tar.gz", hash = "sha256:65cc07e9c6995c5e316109f138570b32da3bd7ff8d0d0ee4aaf2628c3dd8127d"},
{file = "sentry_sdk-2.7.0-py2.py3-none-any.whl", hash = "sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1"},
{file = "sentry_sdk-2.7.0.tar.gz", hash = "sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4"},
]
[package.dependencies]
@ -8399,7 +8388,7 @@ langchain = ["langchain (>=0.0.210)"]
loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"]

View file

@ -147,11 +147,33 @@ ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
minversion = "6.0"
testpaths = ["tests", "integration"]
console_output_style = "progress"
filterwarnings = ["ignore::DeprecationWarning"]
filterwarnings = ["ignore::DeprecationWarning", "ignore::ResourceWarning"]
log_cli = true
markers = ["async_test", "api_key_required"]
[tool.coverage.run]
command_line = """
-m pytest
--cov --cov-report=term --cov-report=html
--instafail -ra -n auto -m "not api_key_required"
tests/unit
"""
source = ["src/backend/base/langflow/"]
omit = ["*/alembic/*", "tests/*", "*/__init__.py"]
[tool.coverage.report]
sort = "Stmts"
skip_empty = true
show_missing = false
ignore_errors = true
[tool.coverage.html]
directory = "coverage"
[tool.ruff]
exclude = ["src/backend/langflow/alembic/*"]
line-length = 120

View file

@ -3,14 +3,16 @@ services:
- type: web
name: langflow
runtime: docker
dockerfilePath: ./docker/render.pre-release.Dockerfile
dockerfilePath: ./docker/render.Dockerfile
repo: https://github.com/langflow-ai/langflow
branch: dev
branch: main
healthCheckPath: /health
autoDeploy: false
envVars:
- key: LANGFLOW_DATABASE_URL
value: sqlite:////home/user/.cache/langflow/langflow.db
- key: LANGFLOW_HOST
value: 0.0.0.0
disk:
name: langflow-data
mountPath: /home/user/.cache/langflow

View file

@ -1950,12 +1950,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -2763,9 +2763,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"

View file

@ -0,0 +1,52 @@
"""Add message table
Revision ID: 325180f0c4e1
Revises: 631faacf5da2
Create Date: 2024-06-23 21:29:28.220100
"""
from typing import Sequence, Union
import sqlalchemy as sa
import sqlmodel
from alembic import op
from langflow.utils import migration
# revision identifiers, used by Alembic.
revision: str = "325180f0c4e1"
down_revision: Union[str, None] = "631faacf5da2"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
# ### commands auto generated by Alembic - please adjust! ###
if not migration.table_exists("message", conn):
op.create_table(
"message",
sa.Column("timestamp", sa.DateTime(), nullable=False),
sa.Column("sender", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("sender_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("session_id", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("text", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("flow_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.Column("files", sa.JSON(), nullable=True),
sa.ForeignKeyConstraint(
["flow_id"],
["flow.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
# ### commands auto generated by Alembic - please adjust! ###
if migration.table_exists("message", conn):
op.drop_table("message")
# ### end Alembic commands ###

View file

@ -276,15 +276,15 @@ async def webhook_run_flow(
# get all webhook components in the flow
webhook_components = get_all_webhook_components_in_flow(flow.data)
tweaks = {}
data_dict = await request.json()
for component in webhook_components:
tweaks[component["id"]] = {"data": data.decode() if isinstance(data, bytes) else data}
input_request = SimplifiedAPIRequest(
input_value=data_dict.get("input_value", ""),
input_type=data_dict.get("input_type", "chat"),
output_type=data_dict.get("output_type", "chat"),
input_value="",
input_type="chat",
output_type="chat",
tweaks=tweaks,
session_id=data_dict.get("session_id"),
session_id=None,
)
logger.debug("Starting background task")
background_tasks.add_task( # type: ignore

View file

@ -131,8 +131,8 @@ async def list_profile_pictures(storage_service: StorageService = Depends(get_st
people = await storage_service.list_files(flow_id=people_path) # type: ignore
space = await storage_service.list_files(flow_id=space_path) # type: ignore
files = [Path("People") / i for i in people]
files += [Path("Space") / i for i in space]
files = [f"People/{i}" for i in people]
files += [f"Space/{i}" for i in space]
return {"files": files}

View file

@ -1,3 +1,4 @@
import re
from datetime import datetime, timezone
from typing import List
from uuid import UUID
@ -46,8 +47,14 @@ def create_flow(
select(Flow).where(Flow.name.like(f"{flow.name} (%")).where(Flow.user_id == current_user.id) # type: ignore
).all()
if flows:
numbers = [int(flow.name.split("(")[1].split(")")[0]) for flow in flows]
flow.name = f"{flow.name} ({max(numbers) + 1})"
extract_number = re.compile(r"\((\d+)\)$")
numbers = []
for _flow in flows:
result = extract_number.search(_flow.name)
if result:
numbers.append(int(result.groups(1)[0]))
if numbers:
flow.name = f"{flow.name} ({max(numbers) + 1})"
else:
flow.name = f"{flow.name} (1)"
# Now check if the endpoint is unique
@ -204,28 +211,11 @@ def update_flow(
if settings_service.settings.remove_api_keys:
flow_data = remove_api_keys(flow_data)
for key, value in flow_data.items():
if value is not None:
setattr(db_flow, key, value)
setattr(db_flow, key, value)
webhook_component = get_webhook_component_in_flow(db_flow.data)
db_flow.webhook = webhook_component is not None
db_flow.updated_at = datetime.now(timezone.utc)
# First check if the flow.name is unique
# there might be flows with name like: "MyFlow", "MyFlow (1)", "MyFlow (2)"
# so we need to check if the name is unique with `like` operator
# if we find a flow with the same name, we add a number to the end of the name
# based on the highest number found
flow_from_db = session.exec(select(Flow).where(Flow.id == flow_id, Flow.user_id == current_user.id)).first()
if flow_from_db:
flows = session.exec(
select(Flow).where(Flow.name.like(f"{flow.name} (%")).where(Flow.user_id == current_user.id) # type: ignore
).all()
if flows:
numbers = [int(flow.name.split("(")[1].split(")")[0]) for flow in flows]
flow.name = f"{flow.name} ({max(numbers) + 1})"
else:
flow.name = f"{flow.name} (1)"
if db_flow.folder_id is None:
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if default_folder:

View file

@ -1,15 +1,15 @@
from typing import List, Optional
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import delete
from sqlmodel import Session, col, select
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import (
MessageModelRequest,
MessageModelResponse,
TransactionModelResponse,
VertexBuildMapModel,
)
from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.message.model import MessageRead, MessageTable, MessageUpdate
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_monitor_service, get_session
from langflow.services.monitor.schema import MessageModelResponse, TransactionModelResponse, VertexBuildMapModel
from langflow.services.monitor.service import MonitorService
router = APIRouter(prefix="/monitor", tags=["Monitor"])
@ -52,45 +52,59 @@ async def get_messages(
sender: Optional[str] = Query(None),
sender_name: Optional[str] = Query(None),
order_by: Optional[str] = Query("timestamp"),
monitor_service: MonitorService = Depends(get_monitor_service),
session: Session = Depends(get_session),
):
try:
df = monitor_service.get_messages(
flow_id=flow_id,
sender=sender,
sender_name=sender_name,
session_id=session_id,
order_by=order_by,
)
dicts = df.to_dict(orient="records")
return [MessageModelResponse(**d) for d in dicts]
stmt = select(MessageTable)
if flow_id:
stmt = stmt.where(MessageTable.flow_id == flow_id)
if session_id:
stmt = stmt.where(MessageTable.session_id == session_id)
if sender:
stmt = stmt.where(MessageTable.sender == sender)
if sender_name:
stmt = stmt.where(MessageTable.sender_name == sender_name)
if order_by:
col = getattr(MessageTable, order_by).asc()
stmt = stmt.order_by(col)
messages = session.exec(stmt)
return [MessageModelResponse.model_validate(d, from_attributes=True) for d in messages]
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/messages", status_code=204)
async def delete_messages(
message_ids: List[int],
monitor_service: MonitorService = Depends(get_monitor_service),
message_ids: List[UUID],
session: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user),
):
try:
monitor_service.delete_messages(message_ids=message_ids)
session.exec(delete(MessageTable).where(MessageTable.id.in_(message_ids))) # type: ignore
session.commit()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/messages/{message_id}", response_model=MessageModelResponse)
@router.put("/messages/{message_id}", response_model=MessageRead)
async def update_message(
message_id: int,
message: MessageModelRequest,
monitor_service: MonitorService = Depends(get_monitor_service),
message_id: UUID,
message: MessageUpdate,
session: Session = Depends(get_session),
user: User = Depends(get_current_active_user),
):
try:
message_dict = message.model_dump(exclude_none=True)
message_dict.pop("index", None)
monitor_service.update_message(message_id=message_id, **message_dict) # type: ignore
return MessageModelResponse(index=message_id, **message_dict)
db_message = session.get(MessageTable, message_id)
if not db_message:
raise HTTPException(status_code=404, detail="Message not found")
message_dict = message.model_dump(exclude_unset=True, exclude_none=True)
db_message.sqlmodel_update(message_dict)
session.add(db_message)
session.commit()
session.refresh(db_message)
return db_message
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@ -98,10 +112,16 @@ async def update_message(
@router.delete("/messages/session/{session_id}", status_code=204)
async def delete_messages_session(
session_id: str,
monitor_service: MonitorService = Depends(get_monitor_service),
session: Session = Depends(get_session),
):
try:
monitor_service.delete_messages_session(session_id=session_id)
session.exec( # type: ignore
delete(MessageTable)
.where(col(MessageTable.session_id) == session_id)
.execution_options(synchronize_session="fetch")
)
session.commit()
return {"message": "Messages deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@ -137,4 +157,3 @@ async def get_transactions(
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
raise HTTPException(status_code=500, detail=str(e))

View file

@ -119,7 +119,11 @@ class LCModelComponent(Component):
return status_message
def get_chat_result(
self, runnable: LanguageModel, stream: bool, input_value: str | Message, system_message: Optional[str] = None
self,
runnable: LanguageModel,
stream: bool,
input_value: str | Message,
system_message: Optional[str] = None,
):
messages: list[Union[BaseMessage]] = []
if not input_value and not system_message:
@ -139,13 +143,13 @@ class LCModelComponent(Component):
messages.append(HumanMessage(content=input_value))
inputs: Union[list, dict] = messages or {}
try:
runnable = runnable.with_config(
{"run_name": self.display_name, "project_name": self._tracing_service.project_name}
runnable = runnable.with_config( # type: ignore
{"run_name": self.display_name, "project_name": self._tracing_service.project_name} # type: ignore
)
if stream:
return runnable.stream(inputs)
return runnable.stream(inputs) # type: ignore
else:
message = runnable.invoke(inputs)
message = runnable.invoke(inputs) # type: ignore
result = message.content if hasattr(message, "content") else message
if isinstance(message, AIMessage):
status_message = self.build_status_message(message)

View file

@ -1 +1 @@
MODEL_NAMES = ["gpt-4o", "gpt-4-turbo", "gpt-4-turbo-preview", "gpt-3.5-turbo", "gpt-3.5-turbo-0125"]
MODEL_NAMES = ["gpt-4o", "gpt-4-turbo", "gpt-4-turbo-preview", "gpt-4", "gpt-3.5-turbo", "gpt-3.5-turbo-0125"]

View file

@ -127,7 +127,7 @@ class ChatLiteLLMModelComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
try:
import litellm # type: ignore
@ -176,5 +176,4 @@ class ChatLiteLLMModelComponent(LCModelComponent):
openrouter_api_key=api_keys["openrouter_api_key"],
)
return output
return output
return output # type: ignore

View file

@ -13,20 +13,39 @@ class AstraVectorize(Component):
VECTORIZE_PROVIDERS_MAPPING = {
"Azure OpenAI": ["azureOpenAI", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
"Hugging Face - Dedicated": ["huggingfaceDedicated", ["endpoint-defined-model"]],
"Hugging Face - Serverless": ["huggingface",
["sentence-transformers/all-MiniLM-L6-v2", "intfloat/multilingual-e5-large",
"intfloat/multilingual-e5-large-instruct", "BAAI/bge-small-en-v1.5",
"BAAI/bge-base-en-v1.5", "BAAI/bge-large-en-v1.5"]],
"Jina AI": ["jinaAI", ["jina-embeddings-v2-base-en", "jina-embeddings-v2-base-de", "jina-embeddings-v2-base-es",
"jina-embeddings-v2-base-code", "jina-embeddings-v2-base-zh"]],
"Hugging Face - Serverless": [
"huggingface",
[
"sentence-transformers/all-MiniLM-L6-v2",
"intfloat/multilingual-e5-large",
"intfloat/multilingual-e5-large-instruct",
"BAAI/bge-small-en-v1.5",
"BAAI/bge-base-en-v1.5",
"BAAI/bge-large-en-v1.5",
],
],
"Jina AI": [
"jinaAI",
[
"jina-embeddings-v2-base-en",
"jina-embeddings-v2-base-de",
"jina-embeddings-v2-base-es",
"jina-embeddings-v2-base-code",
"jina-embeddings-v2-base-zh",
],
],
"Mistral AI": ["mistral", ["mistral-embed"]],
"NVIDIA": ["nvidia", ["NV-Embed-QA"]],
"OpenAI": ["openai", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
"Upstage": ["upstageAI", ["solar-embedding-1-large"]],
"Voyage AI": ["voyageAI",
["voyage-large-2-instruct", "voyage-law-2", "voyage-code-2", "voyage-large-2", "voyage-2"]]
"Voyage AI": [
"voyageAI",
["voyage-large-2-instruct", "voyage-law-2", "voyage-code-2", "voyage-large-2", "voyage-2"],
],
}
VECTORIZE_MODELS_STR = "\n\n".join([provider + ": " + (', '.join(models[1])) for provider, models in VECTORIZE_PROVIDERS_MAPPING.items()])
VECTORIZE_MODELS_STR = "\n\n".join(
[provider + ": " + (", ".join(models[1])) for provider, models in VECTORIZE_PROVIDERS_MAPPING.items()]
)
inputs = [
DropdownInput(
@ -39,13 +58,13 @@ class AstraVectorize(Component):
name="model_name",
display_name="Model name",
info=f"The embedding model to use for the selected provider. Each provider has a different set of models "
f"available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\n{VECTORIZE_MODELS_STR}",
required=True
f"available (full list at https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\n\n{VECTORIZE_MODELS_STR}",
required=True,
),
MessageTextInput(
name="api_key_name",
display_name="API Key name",
info="The name of the embeddings provider API key stored on Astra. If set, it will override the 'ProviderKey' in the authentication parameters."
info="The name of the embeddings provider API key stored on Astra. If set, it will override the 'ProviderKey' in the authentication parameters.",
),
DictInput(
name="authentication",

View file

@ -40,7 +40,7 @@ class OpenAIEmbeddingsComponent(LCEmbeddingsModel):
),
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
SecretStrInput(name="openai_api_base", display_name="OpenAI API Base", advanced=True),
SecretStrInput(name="openai_api_key", display_name="OpenAI API Key"),
SecretStrInput(name="openai_api_key", display_name="OpenAI API Key", value="OPENAI_API_KEY"),
SecretStrInput(name="openai_api_type", display_name="OpenAI API Type", advanced=True),
MessageTextInput(name="openai_api_version", display_name="OpenAI API Version", advanced=True),
MessageTextInput(

View file

@ -0,0 +1,28 @@
from langflow.custom import Component
from langflow.inputs import StrInput
from langflow.schema import Data
from langflow.template import Output
class CreateListComponent(Component):
display_name = "Create List"
description = "Creates a list of texts."
icon = "list"
inputs = [
StrInput(
name="texts",
display_name="Texts",
info="Enter one or more texts.",
is_list=True,
),
]
outputs = [
Output(display_name="Data List", name="list", method="create_list"),
]
def create_list(self) -> list[Data]:
data = [Data(text=text) for text in self.texts]
self.status = data
return data

View file

@ -5,7 +5,7 @@ from langflow.custom import CustomComponent
from langflow.schema.dotdict import dotdict
class UUIDGeneratorComponent(CustomComponent):
class IDGeneratorComponent(CustomComponent):
display_name = "ID Generator"
description = "Generates a unique ID."

View file

@ -1,22 +1,47 @@
from langflow.custom import CustomComponent
from langflow.memory import get_messages, store_message
from langflow.custom import Component
from langflow.inputs import MessageInput, StrInput
from langflow.schema.message import Message
from langflow.template import Output
from langflow.memory import get_messages, store_message
class StoreMessageComponent(CustomComponent):
class StoreMessageComponent(Component):
display_name = "Store Message"
description = "Stores a chat message."
description = "Stores a chat message or text."
icon = "save"
def build_config(self):
return {
"message": {"display_name": "Message"},
}
inputs = [
MessageInput(name="message", display_name="Message", info="The chat message to be stored.", required=True),
StrInput(
name="sender",
display_name="Sender",
info="The sender of the message.",
value="AI",
advanced=True,
),
StrInput(
name="sender_name", display_name="Sender Name", info="The name of the sender.", value="AI", advanced=True
),
StrInput(
name="session_id",
display_name="Session ID",
info="The session ID of the chat.",
value="",
),
]
outputs = [
Output(display_name="Stored Messages", name="stored_messages", method="store_message"),
]
def store_message(self) -> Message:
message = self.message
message.session_id = self.session_id or message.session_id
message.sender = self.sender or message.sender
message.sender_name = self.sender_name or message.sender_name
def build(
self,
message: Message,
) -> Message:
store_message(message, flow_id=self.graph.flow_id)
self.status = get_messages()
return message
stored = get_messages(session_id=message.session_id, sender_name=message.sender_name, sender=message.sender)
self.status = stored
return stored

View file

@ -1,22 +1,25 @@
from .CombineText import CombineTextComponent
from .CustomComponent import CustomComponent
from .FilterData import FilterDataComponent
from .IDGenerator import UUIDGeneratorComponent
from .IDGenerator import IDGeneratorComponent
from .Memory import MemoryComponent
from .MergeData import MergeDataComponent
from .ParseData import ParseDataComponent
from .SplitText import SplitTextComponent
from .StoreMessage import StoreMessageComponent
from .CreateList import CreateListComponent
__all__ = [
"CreateListComponent",
"CombineTextComponent",
"CustomComponent",
"FilterDataComponent",
"UUIDGeneratorComponent",
"IDGeneratorComponent",
"MemoryComponent",
"MergeDataComponent",
"ParseDataComponent",
"SplitTextComponent",
"StoreMessageComponent",
"ListComponent",
]

View file

@ -1,8 +1,9 @@
import uuid
from typing import Optional
from firecrawl.firecrawl import FirecrawlApp
from langflow.custom import CustomComponent
from langflow.schema import Data
import uuid
class FirecrawlCrawlApi(CustomComponent):
display_name: str = "FirecrawlCrawlApi"
@ -47,18 +48,25 @@ class FirecrawlCrawlApi(CustomComponent):
self,
api_key: str,
url: str,
timeout: Optional[int] = 30000,
timeout: int = 30000,
crawlerOptions: Optional[Data] = None,
pageOptions: Optional[Data] = None,
idempotency_key: Optional[str] = None,
) -> Data:
try:
from firecrawl.firecrawl import FirecrawlApp # type: ignore
except ImportError:
raise ImportError(
"Could not import firecrawl integration package. " "Please install it with `pip install firecrawl-py`."
)
if crawlerOptions:
crawler_options_dict = crawlerOptions.__dict__['data']['text']
crawler_options_dict = crawlerOptions.__dict__["data"]["text"]
else:
crawler_options_dict = {}
if pageOptions:
page_options_dict = pageOptions.__dict__['data']['text']
page_options_dict = pageOptions.__dict__["data"]["text"]
else:
page_options_dict = {}
@ -74,7 +82,7 @@ class FirecrawlCrawlApi(CustomComponent):
},
True,
int(timeout / 1000),
idempotency_key
idempotency_key,
)
records = Data(data={"results": crawl_result})

View file

@ -1,8 +1,9 @@
from typing import Optional
from firecrawl.firecrawl import FirecrawlApp
from langflow.custom import CustomComponent
from langflow.schema import Data
class FirecrawlScrapeApi(CustomComponent):
display_name: str = "FirecrawlScrapeApi"
description: str = "Firecrawl Scrape API."
@ -46,13 +47,19 @@ class FirecrawlScrapeApi(CustomComponent):
pageOptions: Optional[Data] = None,
extractorOptions: Optional[Data] = None,
) -> Data:
try:
from firecrawl.firecrawl import FirecrawlApp # type: ignore
except ImportError:
raise ImportError(
"Could not import firecrawl integration package. " "Please install it with `pip install firecrawl-py`."
)
if extractorOptions:
extractor_options_dict = extractorOptions.__dict__['data']['text']
extractor_options_dict = extractorOptions.__dict__["data"]["text"]
else:
extractor_options_dict = {}
if pageOptions:
page_options_dict = pageOptions.__dict__['data']['text']
page_options_dict = pageOptions.__dict__["data"]["text"]
else:
page_options_dict = {}

View file

@ -69,7 +69,7 @@ class AmazonBedrockComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
model_id = self.model_id
credentials_profile_name = self.credentials_profile_name
region_name = self.region_name
@ -89,4 +89,4 @@ class AmazonBedrockComponent(LCModelComponent):
)
except Exception as e:
raise ValueError("Could not connect to AmazonBedrock API.") from e
return output
return output # type: ignore

View file

@ -64,7 +64,7 @@ class AnthropicModelComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
model = self.model
anthropic_api_key = self.anthropic_api_key
max_tokens = self.max_tokens
@ -83,7 +83,7 @@ class AnthropicModelComponent(LCModelComponent):
except Exception as e:
raise ValueError("Could not connect to Anthropic API.") from e
return output
return output # type: ignore
def _get_exception_message(self, exception: Exception) -> str | None:
"""

View file

@ -78,7 +78,7 @@ class AzureChatOpenAIComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="model_response"),
]
def model_response(self) -> LanguageModel:
def model_response(self) -> LanguageModel: # type: ignore[type-var]
model = self.model
azure_endpoint = self.azure_endpoint
azure_deployment = self.azure_deployment
@ -107,4 +107,4 @@ class AzureChatOpenAIComponent(LCModelComponent):
except Exception as e:
raise ValueError("Could not connect to AzureOpenAI API.") from e
return output
return output # type: ignore

View file

@ -51,4 +51,4 @@ class CohereComponent(LCModelComponent):
cohere_api_key=api_key,
)
return output
return output # type: ignore

View file

@ -3,15 +3,7 @@ from pydantic.v1 import SecretStr
from langflow.base.constants import STREAM_INFO_TEXT
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import LanguageModel
from langflow.inputs import (
BoolInput,
DropdownInput,
FloatInput,
IntInput,
MessageInput,
SecretStrInput,
StrInput,
)
from langflow.inputs import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, SecretStrInput, StrInput
class GoogleGenerativeAIComponent(LCModelComponent):
@ -66,7 +58,7 @@ class GoogleGenerativeAIComponent(LCModelComponent):
),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
try:
from langchain_google_genai import ChatGoogleGenerativeAI
except ImportError:
@ -90,4 +82,4 @@ class GoogleGenerativeAIComponent(LCModelComponent):
google_api_key=SecretStr(google_api_key),
)
return output
return output # type: ignore

View file

@ -68,7 +68,7 @@ class GroqModel(LCModelComponent):
),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
groq_api_key = self.groq_api_key
model_name = self.model_name
max_tokens = self.max_tokens
@ -87,4 +87,4 @@ class GroqModel(LCModelComponent):
streaming=stream,
)
return output
return output # type: ignore

View file

@ -36,7 +36,7 @@ class HuggingFaceEndpointsComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
endpoint_url = self.endpoint_url
task = self.task
huggingfacehub_api_token = self.huggingfacehub_api_token
@ -53,4 +53,4 @@ class HuggingFaceEndpointsComponent(LCModelComponent):
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
output = ChatHuggingFace(llm=llm)
return output
return output # type: ignore

View file

@ -70,7 +70,7 @@ class MistralAIModelComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
mistral_api_key = self.mistral_api_key
temperature = self.temperature
model_name = self.model_name
@ -102,4 +102,4 @@ class MistralAIModelComponent(LCModelComponent):
safe_mode=safe_mode,
)
return output
return output # type: ignore

View file

@ -223,7 +223,7 @@ class ChatOllamaComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
# Mapping mirostat settings to their corresponding values
mirostat_options = {"Mirostat": 1, "Mirostat 2.0": 2}
@ -272,4 +272,4 @@ class ChatOllamaComponent(LCModelComponent):
except Exception as e:
raise ValueError("Could not initialize Ollama LLM.") from e
return output
return output # type: ignore

View file

@ -34,6 +34,12 @@ class OpenAIModelComponent(LCModelComponent):
info="The maximum number of tokens to generate. Set to 0 for unlimited tokens.",
),
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
BoolInput(
name="json_mode",
display_name="JSON Mode",
advanced=True,
info="If True, it will output JSON regardless of passing a schema.",
),
DictInput(
name="output_schema",
is_list=True,
@ -74,8 +80,8 @@ class OpenAIModelComponent(LCModelComponent):
),
]
def build_model(self) -> LanguageModel:
# self.output_schea is a list of dictionaries
def build_model(self) -> LanguageModel: # type: ignore[type-var]
# self.output_schea is a list of dictionarie s
# let's convert it to a dictionary
output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})
openai_api_key = self.openai_api_key
@ -84,7 +90,7 @@ class OpenAIModelComponent(LCModelComponent):
max_tokens = self.max_tokens
model_kwargs = self.model_kwargs or {}
openai_api_base = self.openai_api_base or "https://api.openai.com/v1"
json_mode = bool(output_schema_dict)
json_mode = bool(output_schema_dict) or self.json_mode
seed = self.seed
model_kwargs["seed"] = seed
@ -101,9 +107,12 @@ class OpenAIModelComponent(LCModelComponent):
temperature=temperature or 0.1,
)
if json_mode:
output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore
if output_schema_dict:
output = output.with_structured_output(schema=output_schema_dict, method="json_mode") # type: ignore
else:
output = output.bind(response_format={"type": "json_object"}) # type: ignore
return output
return output # type: ignore
def _get_exception_message(self, e: Exception):
"""

View file

@ -52,7 +52,7 @@ class ChatVertexAIComponent(LCModelComponent):
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def build_model(self) -> LanguageModel:
def build_model(self) -> LanguageModel: # type: ignore[type-var]
credentials = self.credentials
location = self.location
max_output_tokens = self.max_output_tokens
@ -75,4 +75,4 @@ class ChatVertexAIComponent(LCModelComponent):
verbose=verbose,
)
return output
return output # type: ignore

View file

@ -66,21 +66,17 @@ class ConditionalRouterComponent(Component):
def true_response(self) -> Message:
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
if result:
self.stop("false_result")
response = self.message if self.message else self.input_text
self.status = response
return response
self.status = self.message
return self.message
else:
self.stop("true_result")
return Message()
return None # type: ignore
def false_response(self) -> Message:
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
if not result:
self.stop("true_result")
response = self.message if self.message else self.input_text
self.status = response
return response
self.status = self.message
return self.message
else:
self.stop("false_result")
return Message()
return None # type: ignore

View file

@ -1,31 +1,32 @@
from typing import Union
from langflow.custom import CustomComponent
from langflow.field_typing import Text
from langflow.schema import Data
from langflow.custom import Component
from langflow.io import MessageInput
from langflow.schema.message import Message
from langflow.template import Output
class PassComponent(CustomComponent):
class PassMessageComponent(Component):
display_name = "Pass"
description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction."
field_order = ["ignored_input", "forwarded_input"]
beta = True
description = "Forwards the input message, unchanged."
icon = "arrow-right"
def build_config(self) -> dict:
return {
"ignored_input": {
"display_name": "Ignored Input",
"info": "This input is ignored. It's used to control the flow in the graph.",
"input_types": ["Text", "Data"],
},
"forwarded_input": {
"display_name": "Input",
"info": "This input is forwarded by the component.",
"input_types": ["Text", "Data"],
},
}
inputs = [
MessageInput(
name="input_message",
display_name="Input Message",
info="The message to be passed forward.",
),
MessageInput(
name="ignored_message",
display_name="Ignored Message",
info="A second message to be ignored. Used as a workaround for continuity.",
advanced=True,
),
]
def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Data]:
# The ignored_input is not used in the logic, it's just there for graph flow control
self.status = forwarded_input
return forwarded_input
outputs = [
Output(display_name="Output Message", name="output_message", method="pass_message"),
]
def pass_message(self) -> Message:
self.status = self.input_message
return self.input_message

View file

@ -2,7 +2,7 @@ from .ConditionalRouter import ConditionalRouterComponent
from .FlowTool import FlowToolComponent
from .Listen import ListenComponent
from .Notify import NotifyComponent
from .Pass import PassComponent
from .Pass import PassMessageComponent
from .PythonFunction import PythonFunctionComponent
from .RunFlow import RunFlowComponent
from .RunnableExecutor import RunnableExecComponent
@ -16,7 +16,7 @@ __all__ = [
"FlowToolComponent",
"ListenComponent",
"NotifyComponent",
"PassComponent",
"PassMessageComponent",
"PythonFunctionComponent",
"RunFlowComponent",
"RunnableExecComponent",

View file

@ -24,18 +24,20 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
icon = "Cassandra"
inputs = [
MessageTextInput(name="database_ref",
display_name="Contact Points / Astra Database ID",
info="Contact points for the database (or AstraDB database ID)",
required=True),
MessageTextInput(name="username",
display_name="Username",
info="Username for the database (leave empty for AstraDB)."),
MessageTextInput(
name="database_ref",
display_name="Contact Points / Astra Database ID",
info="Contact points for the database (or AstraDB database ID)",
required=True,
),
MessageTextInput(
name="username", display_name="Username", info="Username for the database (leave empty for AstraDB)."
),
SecretStrInput(
name="token",
display_name="Password / AstraDB Token",
info="User password for the database (or AstraDB token).",
required=True
required=True,
),
MessageTextInput(
name="keyspace",
@ -81,7 +83,7 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
display_name="Cluster arguments",
info="Optional dictionary of additional keyword arguments for the Cassandra cluster.",
advanced=True,
is_list=True
is_list=True,
),
MultilineInput(name="search_query", display_name="Search Query"),
DataInput(
@ -137,7 +139,7 @@ class CassandraVectorStoreComponent(LCVectorStoreComponent):
cluster_kwargs=self.cluster_kwargs,
)
if not self.ttl_seconds:
if not self.ttl_seconds: # type: ignore
self.ttl_seconds = None
documents = []

View file

@ -329,8 +329,6 @@ class CodeParser:
"""
Extracts "classes" from the code, including inheritance and init methods.
"""
if node.name in ["CustomComponent", "Component", "BaseComponent"]:
return
bases = self.get_base_classes()
nodes = []
for base in bases:

View file

@ -83,7 +83,7 @@ class CustomComponent(BaseComponent):
_flows_data: Optional[List[Data]] = None
_outputs: List[OutputLog] = []
_logs: List[Log] = []
_tracing_service: "TracingService"
tracing_service: Optional["TracingService"] = None
def update_state(self, name: str, value: Any):
if not self.vertex:
@ -96,7 +96,7 @@ class CustomComponent(BaseComponent):
def stop(self, output_name: str | None = None):
if not output_name and self.vertex and len(self.vertex.outputs) == 1:
output_name = self.vertex.outputs[0]["name"]
else:
elif not output_name:
raise ValueError("You must specify an output name to call stop")
if not self.vertex:
raise ValueError("Vertex is not set")
@ -488,14 +488,14 @@ class CustomComponent(BaseComponent):
Args:
message (LoggableType | list[LoggableType]): The message to log.
"""
if name is None:
name = self.display_name if self.display_name else self.__class__.__name__
if hasattr(message, "model_dump") and isinstance(message, BaseModel):
message = message.model_dump()
if name is None and self.display_name:
name = self.display_name
else:
name = self.__class__.__name__
log = Log(message=message, type=get_artifact_type(message), name=name)
self._logs.append(log)
if self.vertex:
self._tracing_service.add_log(trace_name=self.vertex.id, log=log)
if self.tracing_service and self.vertex:
self.tracing_service.add_log(trace_name=self.vertex.id, log=log)
def post_code_processing(self, new_build_config: dict, current_build_config: dict):
"""

View file

@ -26,6 +26,7 @@ from .constants import (
TextSplitter,
Tool,
VectorStore,
LanguageModel,
)
from .range_spec import RangeSpec
@ -84,4 +85,5 @@ __all__ = [
"BaseChatModel",
"Retriever",
"Text",
"LanguageModel",
]

View file

@ -343,10 +343,10 @@ class Graph:
except Exception as exc:
logger.exception(exc)
tb = traceback.format_exc()
await self.end_all_traces(error=f"{exc.__class__.__name__}: {exc}\n\n{tb}")
asyncio.create_task(self.end_all_traces(error=f"{exc.__class__.__name__}: {exc}\n\n{tb}"))
raise ValueError(f"Error running graph: {exc}") from exc
finally:
await self.end_all_traces()
asyncio.create_task(self.end_all_traces())
# Get the outputs
vertex_outputs = []
for vertex in self.vertices:
@ -1208,6 +1208,7 @@ class Graph:
except ValueError:
stop_or_start_vertex = self.get_root_of_group_node(vertex_id)
stack = [stop_or_start_vertex.id]
vertex_id = stop_or_start_vertex.id
stop_predecessors = [pre.id for pre in stop_or_start_vertex.predecessors]
# DFS to collect all vertices that can reach the specified vertex
while stack:
@ -1443,7 +1444,7 @@ class Graph:
def is_vertex_runnable(self, vertex_id: str) -> bool:
"""Returns whether a vertex is runnable."""
return self.run_manager.is_vertex_runnable(vertex_id)
return self.run_manager.is_vertex_runnable(vertex_id, self.inactivated_vertices)
def build_run_map(self):
"""
@ -1463,7 +1464,7 @@ class Graph:
This checks the direct predecessors of each successor to identify any that are
immediately runnable, expanding the search to ensure progress can be made.
"""
return self.run_manager.find_runnable_predecessors_for_successors(vertex_id)
return self.run_manager.find_runnable_predecessors_for_successors(vertex_id, self.inactivated_vertices)
def remove_from_predecessors(self, vertex_id: str):
self.run_manager.remove_from_predecessors(vertex_id)

View file

@ -1,6 +1,6 @@
import asyncio
from collections import defaultdict
from typing import TYPE_CHECKING, Callable, List, Coroutine
from typing import TYPE_CHECKING, Callable, Coroutine, List
if TYPE_CHECKING:
from langflow.graph.graph.base import Graph
@ -40,19 +40,23 @@ class RunnableVerticesManager:
self.run_predecessors = state["run_predecessors"]
self.vertices_to_run = state["vertices_to_run"]
def is_vertex_runnable(self, vertex_id: str) -> bool:
def is_vertex_runnable(self, vertex_id: str, inactivated_vertices: set[str]) -> bool:
"""Determines if a vertex is runnable."""
return vertex_id in self.vertices_to_run and not self.run_predecessors.get(vertex_id)
return (
vertex_id in self.vertices_to_run
and not self.run_predecessors.get(vertex_id)
and vertex_id not in inactivated_vertices
)
def find_runnable_predecessors_for_successors(self, vertex_id: str) -> List[str]:
def find_runnable_predecessors_for_successors(self, vertex_id: str, inactivated_vertices: set[str]) -> List[str]:
"""Finds runnable predecessors for the successors of a given vertex."""
runnable_vertices = []
visited = set()
for successor_id in self.run_map.get(vertex_id, []):
for predecessor_id in self.run_predecessors.get(successor_id, []):
if predecessor_id not in visited and self.is_vertex_runnable(predecessor_id):
if predecessor_id not in visited and self.is_vertex_runnable(predecessor_id, inactivated_vertices):
runnable_vertices.append(predecessor_id)
visited.add(predecessor_id)
return runnable_vertices
@ -104,10 +108,14 @@ class RunnableVerticesManager:
"""
async with lock:
self.remove_from_predecessors(vertex.id)
direct_successors_ready = [v for v in vertex.successors_ids if self.is_vertex_runnable(v)]
direct_successors_ready = [
v for v in vertex.successors_ids if self.is_vertex_runnable(v, graph.inactivated_vertices)
]
if not direct_successors_ready:
# No direct successors ready, look for runnable predecessors of successors
next_runnable_vertices = self.find_runnable_predecessors_for_successors(vertex.id)
next_runnable_vertices = self.find_runnable_predecessors_for_successors(
vertex.id, graph.inactivated_vertices
)
else:
next_runnable_vertices = direct_successors_ready

View file

@ -2,10 +2,11 @@
"data": {
"edges": [
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "ChatInput",
"id": "ChatInput-pxptT",
"id": "ChatInput-Y6mi1",
"name": "message",
"output_types": [
"Message"
@ -13,7 +14,7 @@
},
"targetHandle": {
"fieldName": "user_input",
"id": "Prompt-1S5SU",
"id": "Prompt-Z4WYI",
"inputTypes": [
"Message",
"Text"
@ -21,17 +22,18 @@
"type": "str"
}
},
"id": "reactflow__edge-ChatInput-pxptT{œdataTypeœ:œChatInputœ,œidœ:œChatInput-pxptTœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-1S5SU{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-1S5SUœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ChatInput-pxptT",
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-pxptTœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-1S5SU",
"targetHandle": "{œfieldNameœ: œuser_inputœ, œidœ: œPrompt-1S5SUœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-ChatInput-Y6mi1{œdataTypeœ:œChatInputœ,œidœ:œChatInput-Y6mi1œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-Z4WYI{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-Z4WYIœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ChatInput-Y6mi1",
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-Y6mi1œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-Z4WYI",
"targetHandle": "{œfieldNameœ: œuser_inputœ, œidœ: œPrompt-Z4WYIœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "Prompt",
"id": "Prompt-1S5SU",
"id": "Prompt-Z4WYI",
"name": "prompt",
"output_types": [
"Message"
@ -39,24 +41,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "OpenAIModel-nJXWj",
"id": "OpenAIModel-26Eve",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-Prompt-1S5SU{œdataTypeœ:œPromptœ,œidœ:œPrompt-1S5SUœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-nJXWj{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-nJXWjœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-1S5SU",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-1S5SUœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-nJXWj",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-nJXWjœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-Prompt-Z4WYI{œdataTypeœ:œPromptœ,œidœ:œPrompt-Z4WYIœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-26Eve{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-26Eveœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-Z4WYI",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-Z4WYIœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-26Eve",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-26Eveœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
},
{
"data": {
"sourceHandle": {
"dataType": "OpenAIModel",
"id": "OpenAIModel-nJXWj",
"dataType": "OpenAIModelComponent",
"id": "OpenAIModel-26Eve",
"name": "text_output",
"output_types": [
"Message"
@ -64,24 +66,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-XP4bj",
"id": "ChatOutput-cQnVI",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-OpenAIModel-nJXWj{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-nJXWjœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-XP4bj{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-XP4bjœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-nJXWj",
"sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-nJXWjœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-XP4bj",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-XP4bjœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-OpenAIModel-26Eve{œdataTypeœ:œOpenAIModelComponentœ,œidœ:œOpenAIModel-26Eveœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-cQnVI{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-cQnVIœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-26Eve",
"sourceHandle": "{œdataTypeœ: œOpenAIModelComponentœ, œidœ: œOpenAIModel-26Eveœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-cQnVI",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-cQnVIœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
}
],
"nodes": [
{
"data": {
"id": "ChatInput-pxptT",
"id": "ChatInput-Y6mi1",
"node": {
"base_classes": [
"Message"
@ -264,7 +266,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatInput-pxptT",
"id": "ChatInput-Y6mi1",
"position": {
"x": -493.6459512396177,
"y": 1083.200545525551
@ -281,7 +283,7 @@
"data": {
"description": "Create a prompt template with dynamic variables.",
"display_name": "Prompt",
"id": "Prompt-1S5SU",
"id": "Prompt-Z4WYI",
"node": {
"base_classes": [
"Message"
@ -389,7 +391,7 @@
},
"dragging": false,
"height": 422,
"id": "Prompt-1S5SU",
"id": "Prompt-Z4WYI",
"position": {
"x": 56.354011530798516,
"y": 1157.2005405164796
@ -404,7 +406,10 @@
},
{
"data": {
"id": "OpenAIModel-nJXWj",
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"edited": false,
"id": "OpenAIModel-26Eve",
"node": {
"base_classes": [
"LanguageModel",
@ -416,11 +421,12 @@
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"documentation": "",
"edited": false,
"edited": true,
"field_order": [
"input_value",
"max_tokens",
"model_kwargs",
"json_mode",
"output_schema",
"model_name",
"openai_api_base",
@ -477,7 +483,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict)\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
},
"input_value": {
"advanced": false,
@ -499,6 +505,21 @@
"type": "str",
"value": ""
},
"json_mode": {
"advanced": true,
"display_name": "JSON Mode",
"dynamic": false,
"info": "If True, it will output JSON regardless of passing a schema.",
"list": false,
"name": "json_mode",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"max_tokens": {
"advanced": true,
"display_name": "Max Tokens",
@ -572,7 +593,7 @@
"dynamic": false,
"info": "The OpenAI API Key to use for the OpenAI model.",
"input_types": [],
"load_from_db": false,
"load_from_db": true,
"name": "openai_api_key",
"password": true,
"placeholder": "",
@ -580,7 +601,7 @@
"show": true,
"title_case": false,
"type": "str",
"value": ""
"value": "OPENAI_API_KEY"
},
"output_schema": {
"advanced": true,
@ -660,11 +681,11 @@
}
}
},
"type": "OpenAIModel"
"type": "OpenAIModelComponent"
},
"dragging": false,
"height": 621,
"id": "OpenAIModel-nJXWj",
"id": "OpenAIModel-26Eve",
"position": {
"x": 624.3539730827923,
"y": 1053.2005475562555
@ -679,7 +700,7 @@
},
{
"data": {
"id": "ChatOutput-XP4bj",
"id": "ChatOutput-cQnVI",
"node": {
"base_classes": [
"Message"
@ -839,7 +860,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatOutput-XP4bj",
"id": "ChatOutput-cQnVI",
"position": {
"x": 1219.477374823274,
"y": 1200.950216973985
@ -854,15 +875,15 @@
}
],
"viewport": {
"x": 392.1085223509972,
"y": -327.49805229761307,
"zoom": 0.5000000676901589
"x": 366.93776265249005,
"y": -343.56726676261223,
"zoom": 0.5000000676901587
}
},
"description": "This flow will get you experimenting with the basics of the UI, the Chat and the Prompt component. \n\nTry changing the Template in it to see how the model behaves. \nYou can change it to this and a Text Input into the `type_of_person` variable : \"Answer the user as if you were a pirate.\n\nUser: {user_input}\n\nAnswer: \" ",
"endpoint_name": null,
"id": "f652abdc-7ef2-4e52-a00b-847b7aa32cee",
"id": "e533253b-818b-4b5a-9793-55ab83fffb07",
"is_component": false,
"last_tested_version": "1.0.0rc1",
"last_tested_version": "1.0.5",
"name": "Basic Prompting (Hello, World)"
}

View file

@ -2,10 +2,11 @@
"data": {
"edges": [
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "URL",
"id": "URL-k9NkE",
"id": "URL-rETJU",
"name": "data",
"output_types": [
"Data"
@ -13,24 +14,25 @@
},
"targetHandle": {
"fieldName": "data",
"id": "ParseData-EwWXd",
"id": "ParseData-AqSfN",
"inputTypes": [
"Data"
],
"type": "other"
}
},
"id": "reactflow__edge-URL-k9NkE{œdataTypeœ:œURLœ,œidœ:œURL-k9NkEœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-EwWXd{œfieldNameœ:œdataœ,œidœ:œParseData-EwWXdœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
"source": "URL-k9NkE",
"sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-k9NkEœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
"target": "ParseData-EwWXd",
"targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-EwWXdœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
"id": "reactflow__edge-URL-rETJU{œdataTypeœ:œURLœ,œidœ:œURL-rETJUœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-AqSfN{œfieldNameœ:œdataœ,œidœ:œParseData-AqSfNœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
"source": "URL-rETJU",
"sourceHandle": "{œdataTypeœ: œURLœ, œidœ: œURL-rETJUœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
"target": "ParseData-AqSfN",
"targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-AqSfNœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "ParseData",
"id": "ParseData-EwWXd",
"id": "ParseData-AqSfN",
"name": "text",
"output_types": [
"Message"
@ -38,7 +40,7 @@
},
"targetHandle": {
"fieldName": "references",
"id": "Prompt-B9Mq6",
"id": "Prompt-rizUK",
"inputTypes": [
"Message",
"Text"
@ -46,17 +48,18 @@
"type": "str"
}
},
"id": "reactflow__edge-ParseData-EwWXd{œdataTypeœ:œParseDataœ,œidœ:œParseData-EwWXdœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-B9Mq6{œfieldNameœ:œreferencesœ,œidœ:œPrompt-B9Mq6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ParseData-EwWXd",
"sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-EwWXdœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-B9Mq6",
"targetHandle": "{œfieldNameœ: œreferencesœ, œidœ: œPrompt-B9Mq6œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-ParseData-AqSfN{œdataTypeœ:œParseDataœ,œidœ:œParseData-AqSfNœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-rizUK{œfieldNameœ:œreferencesœ,œidœ:œPrompt-rizUKœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ParseData-AqSfN",
"sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-AqSfNœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-rizUK",
"targetHandle": "{œfieldNameœ: œreferencesœ, œidœ: œPrompt-rizUKœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "TextInput",
"id": "TextInput-uf6ij",
"id": "TextInput-OffFR",
"name": "text",
"output_types": [
"Message"
@ -64,7 +67,7 @@
},
"targetHandle": {
"fieldName": "instructions",
"id": "Prompt-B9Mq6",
"id": "Prompt-rizUK",
"inputTypes": [
"Message",
"Text"
@ -72,17 +75,18 @@
"type": "str"
}
},
"id": "reactflow__edge-TextInput-uf6ij{œdataTypeœ:œTextInputœ,œidœ:œTextInput-uf6ijœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-B9Mq6{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-B9Mq6œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "TextInput-uf6ij",
"sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-uf6ijœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-B9Mq6",
"targetHandle": "{œfieldNameœ: œinstructionsœ, œidœ: œPrompt-B9Mq6œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-TextInput-OffFR{œdataTypeœ:œTextInputœ,œidœ:œTextInput-OffFRœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-rizUK{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-rizUKœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "TextInput-OffFR",
"sourceHandle": "{œdataTypeœ: œTextInputœ, œidœ: œTextInput-OffFRœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-rizUK",
"targetHandle": "{œfieldNameœ: œinstructionsœ, œidœ: œPrompt-rizUKœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "Prompt",
"id": "Prompt-B9Mq6",
"id": "Prompt-rizUK",
"name": "prompt",
"output_types": [
"Message"
@ -90,24 +94,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "OpenAIModel-X9ukk",
"id": "OpenAIModel-qmhKV",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-Prompt-B9Mq6{œdataTypeœ:œPromptœ,œidœ:œPrompt-B9Mq6œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-X9ukk{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-X9ukkœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-B9Mq6",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-B9Mq6œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-X9ukk",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-X9ukkœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-Prompt-rizUK{œdataTypeœ:œPromptœ,œidœ:œPrompt-rizUKœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-qmhKV{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-qmhKVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-rizUK",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-rizUKœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-qmhKV",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-qmhKVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
},
{
"data": {
"sourceHandle": {
"dataType": "OpenAIModel",
"id": "OpenAIModel-X9ukk",
"dataType": "OpenAIModelComponent",
"id": "OpenAIModel-qmhKV",
"name": "text_output",
"output_types": [
"Message"
@ -115,24 +119,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-5r5Iw",
"id": "ChatOutput-W684s",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-OpenAIModel-X9ukk{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-X9ukkœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-5r5Iw{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-5r5Iwœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-X9ukk",
"sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-X9ukkœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-5r5Iw",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-5r5Iwœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-OpenAIModel-qmhKV{œdataTypeœ:œOpenAIModelComponentœ,œidœ:œOpenAIModel-qmhKVœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-W684s{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-W684sœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-qmhKV",
"sourceHandle": "{œdataTypeœ: œOpenAIModelComponentœ, œidœ: œOpenAIModel-qmhKVœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-W684s",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-W684sœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
}
],
"nodes": [
{
"data": {
"id": "URL-k9NkE",
"id": "URL-rETJU",
"node": {
"base_classes": [
"Data"
@ -214,7 +218,7 @@
},
"dragging": false,
"height": 358,
"id": "URL-k9NkE",
"id": "URL-rETJU",
"position": {
"x": 220.79156431407534,
"y": 498.8186168722667
@ -229,7 +233,7 @@
},
{
"data": {
"id": "ParseData-EwWXd",
"id": "ParseData-AqSfN",
"node": {
"base_classes": [
"Message"
@ -346,7 +350,7 @@
},
"dragging": false,
"height": 384,
"id": "ParseData-EwWXd",
"id": "ParseData-AqSfN",
"position": {
"x": 754.3607306709101,
"y": 736.8516961537598
@ -363,7 +367,7 @@
"data": {
"description": "Create a prompt template with dynamic variables.",
"display_name": "Prompt",
"id": "Prompt-B9Mq6",
"id": "Prompt-rizUK",
"node": {
"base_classes": [
"Message"
@ -496,7 +500,7 @@
},
"dragging": false,
"height": 515,
"id": "Prompt-B9Mq6",
"id": "Prompt-rizUK",
"position": {
"x": 1368.0633591447076,
"y": 467.19448061224284
@ -511,7 +515,7 @@
},
{
"data": {
"id": "TextInput-uf6ij",
"id": "TextInput-OffFR",
"node": {
"base_classes": [
"Message"
@ -590,7 +594,7 @@
},
"dragging": false,
"height": 308,
"id": "TextInput-uf6ij",
"id": "TextInput-OffFR",
"position": {
"x": 743.7338453293725,
"y": 301.58775454952183
@ -605,7 +609,10 @@
},
{
"data": {
"id": "OpenAIModel-X9ukk",
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"edited": false,
"id": "OpenAIModel-qmhKV",
"node": {
"base_classes": [
"LanguageModel",
@ -617,11 +624,12 @@
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"documentation": "",
"edited": false,
"edited": true,
"field_order": [
"input_value",
"max_tokens",
"model_kwargs",
"json_mode",
"output_schema",
"model_name",
"openai_api_base",
@ -678,7 +686,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict)\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
},
"input_value": {
"advanced": false,
@ -700,6 +708,21 @@
"type": "str",
"value": ""
},
"json_mode": {
"advanced": true,
"display_name": "JSON Mode",
"dynamic": false,
"info": "If True, it will output JSON regardless of passing a schema.",
"list": false,
"name": "json_mode",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"max_tokens": {
"advanced": true,
"display_name": "Max Tokens",
@ -773,7 +796,7 @@
"dynamic": false,
"info": "The OpenAI API Key to use for the OpenAI model.",
"input_types": [],
"load_from_db": false,
"load_from_db": true,
"name": "openai_api_key",
"password": true,
"placeholder": "",
@ -781,7 +804,7 @@
"show": true,
"title_case": false,
"type": "str",
"value": ""
"value": "OPENAI_API_KEY"
},
"output_schema": {
"advanced": true,
@ -861,11 +884,11 @@
}
}
},
"type": "OpenAIModel"
"type": "OpenAIModelComponent"
},
"dragging": false,
"height": 621,
"id": "OpenAIModel-X9ukk",
"id": "OpenAIModel-qmhKV",
"position": {
"x": 1899.407626221589,
"y": 395.9013619556682
@ -880,7 +903,7 @@
},
{
"data": {
"id": "ChatOutput-5r5Iw",
"id": "ChatOutput-W684s",
"node": {
"base_classes": [
"Message"
@ -1040,7 +1063,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatOutput-5r5Iw",
"id": "ChatOutput-W684s",
"position": {
"x": 2449.3489426461606,
"y": 571.2449700910389
@ -1062,8 +1085,8 @@
},
"description": "This flow can be used to create a blog post following instructions from the user, using two other blogs as reference.",
"endpoint_name": null,
"id": "13da3150-95b9-4d81-9ad2-f635dcdce7ab",
"id": "da9999f8-9013-4bd9-8adb-653c94ebf08c",
"is_component": false,
"last_tested_version": "1.0.0rc1",
"last_tested_version": "1.0.5",
"name": "Blog Writer"
}

View file

@ -2,10 +2,11 @@
"data": {
"edges": [
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "File",
"id": "File-h46aK",
"id": "File-Q3Xrb",
"name": "data",
"output_types": [
"Data"
@ -13,24 +14,25 @@
},
"targetHandle": {
"fieldName": "data",
"id": "ParseData-sqVr1",
"id": "ParseData-1Y5jJ",
"inputTypes": [
"Data"
],
"type": "other"
}
},
"id": "reactflow__edge-File-h46aK{œdataTypeœ:œFileœ,œidœ:œFile-h46aKœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-sqVr1{œfieldNameœ:œdataœ,œidœ:œParseData-sqVr1œ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
"source": "File-h46aK",
"sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-h46aKœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
"target": "ParseData-sqVr1",
"targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-sqVr1œ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
"id": "reactflow__edge-File-Q3Xrb{œdataTypeœ:œFileœ,œidœ:œFile-Q3Xrbœ,œnameœ:œdataœ,œoutput_typesœ:[œDataœ]}-ParseData-1Y5jJ{œfieldNameœ:œdataœ,œidœ:œParseData-1Y5jJœ,œinputTypesœ:[œDataœ],œtypeœ:œotherœ}",
"source": "File-Q3Xrb",
"sourceHandle": "{œdataTypeœ: œFileœ, œidœ: œFile-Q3Xrbœ, œnameœ: œdataœ, œoutput_typesœ: [œDataœ]}",
"target": "ParseData-1Y5jJ",
"targetHandle": "{œfieldNameœ: œdataœ, œidœ: œParseData-1Y5jJœ, œinputTypesœ: [œDataœ], œtypeœ: œotherœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "ParseData",
"id": "ParseData-sqVr1",
"id": "ParseData-1Y5jJ",
"name": "text",
"output_types": [
"Message"
@ -38,7 +40,7 @@
},
"targetHandle": {
"fieldName": "Document",
"id": "Prompt-mQ7w2",
"id": "Prompt-CMJEB",
"inputTypes": [
"Message",
"Text"
@ -46,17 +48,18 @@
"type": "str"
}
},
"id": "reactflow__edge-ParseData-sqVr1{œdataTypeœ:œParseDataœ,œidœ:œParseData-sqVr1œ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-mQ7w2{œfieldNameœ:œDocumentœ,œidœ:œPrompt-mQ7w2œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ParseData-sqVr1",
"sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-sqVr1œ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-mQ7w2",
"targetHandle": "{œfieldNameœ: œDocumentœ, œidœ: œPrompt-mQ7w2œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-ParseData-1Y5jJ{œdataTypeœ:œParseDataœ,œidœ:œParseData-1Y5jJœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-CMJEB{œfieldNameœ:œDocumentœ,œidœ:œPrompt-CMJEBœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ParseData-1Y5jJ",
"sourceHandle": "{œdataTypeœ: œParseDataœ, œidœ: œParseData-1Y5jJœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-CMJEB",
"targetHandle": "{œfieldNameœ: œDocumentœ, œidœ: œPrompt-CMJEBœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "ChatInput",
"id": "ChatInput-cMXe0",
"id": "ChatInput-mc7sJ",
"name": "message",
"output_types": [
"Message"
@ -64,7 +67,7 @@
},
"targetHandle": {
"fieldName": "Question",
"id": "Prompt-mQ7w2",
"id": "Prompt-CMJEB",
"inputTypes": [
"Message",
"Text"
@ -72,17 +75,18 @@
"type": "str"
}
},
"id": "reactflow__edge-ChatInput-cMXe0{œdataTypeœ:œChatInputœ,œidœ:œChatInput-cMXe0œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-mQ7w2{œfieldNameœ:œQuestionœ,œidœ:œPrompt-mQ7w2œ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ChatInput-cMXe0",
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-cMXe0œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-mQ7w2",
"targetHandle": "{œfieldNameœ: œQuestionœ, œidœ: œPrompt-mQ7w2œ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-ChatInput-mc7sJ{œdataTypeœ:œChatInputœ,œidœ:œChatInput-mc7sJœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-CMJEB{œfieldNameœ:œQuestionœ,œidœ:œPrompt-CMJEBœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ChatInput-mc7sJ",
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-mc7sJœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-CMJEB",
"targetHandle": "{œfieldNameœ: œQuestionœ, œidœ: œPrompt-CMJEBœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "Prompt",
"id": "Prompt-mQ7w2",
"id": "Prompt-CMJEB",
"name": "prompt",
"output_types": [
"Message"
@ -90,24 +94,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "OpenAIModel-O0AGC",
"id": "OpenAIModel-U2g5u",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-Prompt-mQ7w2{œdataTypeœ:œPromptœ,œidœ:œPrompt-mQ7w2œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-O0AGC{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-O0AGCœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-mQ7w2",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-mQ7w2œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-O0AGC",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-O0AGCœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-Prompt-CMJEB{œdataTypeœ:œPromptœ,œidœ:œPrompt-CMJEBœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-U2g5u{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-U2g5uœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-CMJEB",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-CMJEBœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-U2g5u",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-U2g5uœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
},
{
"data": {
"sourceHandle": {
"dataType": "OpenAIModel",
"id": "OpenAIModel-O0AGC",
"dataType": "OpenAIModelComponent",
"id": "OpenAIModel-U2g5u",
"name": "text_output",
"output_types": [
"Message"
@ -115,24 +119,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-efggd",
"id": "ChatOutput-yZjPO",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-OpenAIModel-O0AGC{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-O0AGCœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-efggd{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-efggdœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-O0AGC",
"sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-O0AGCœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-efggd",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-efggdœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-OpenAIModel-U2g5u{œdataTypeœ:œOpenAIModelComponentœ,œidœ:œOpenAIModel-U2g5uœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-yZjPO{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-yZjPOœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-U2g5u",
"sourceHandle": "{œdataTypeœ: œOpenAIModelComponentœ, œidœ: œOpenAIModel-U2g5uœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-yZjPO",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-yZjPOœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
}
],
"nodes": [
{
"data": {
"id": "File-h46aK",
"id": "File-Q3Xrb",
"node": {
"base_classes": [
"Data"
@ -243,7 +247,7 @@
},
"dragging": false,
"height": 300,
"id": "File-h46aK",
"id": "File-Q3Xrb",
"position": {
"x": -449.0807503257012,
"y": -253.5304920926106
@ -258,7 +262,7 @@
},
{
"data": {
"id": "ParseData-sqVr1",
"id": "ParseData-1Y5jJ",
"node": {
"base_classes": [
"Message"
@ -375,7 +379,7 @@
},
"dragging": false,
"height": 384,
"id": "ParseData-sqVr1",
"id": "ParseData-1Y5jJ",
"position": {
"x": 73.79471204296345,
"y": -186.9430114986888
@ -392,7 +396,7 @@
"data": {
"description": "Create a prompt template with dynamic variables.",
"display_name": "Prompt",
"id": "Prompt-mQ7w2",
"id": "Prompt-CMJEB",
"node": {
"base_classes": [
"Message"
@ -525,7 +529,7 @@
},
"dragging": false,
"height": 515,
"id": "Prompt-mQ7w2",
"id": "Prompt-CMJEB",
"position": {
"x": 637.3518652087848,
"y": 47.191730368560215
@ -540,7 +544,7 @@
},
{
"data": {
"id": "ChatInput-cMXe0",
"id": "ChatInput-mc7sJ",
"node": {
"base_classes": [
"Message"
@ -723,7 +727,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatInput-cMXe0",
"id": "ChatInput-mc7sJ",
"position": {
"x": 50.08709924122684,
"y": 320.88186720121615
@ -738,7 +742,10 @@
},
{
"data": {
"id": "OpenAIModel-O0AGC",
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"edited": false,
"id": "OpenAIModel-U2g5u",
"node": {
"base_classes": [
"LanguageModel",
@ -750,11 +757,12 @@
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"documentation": "",
"edited": false,
"edited": true,
"field_order": [
"input_value",
"max_tokens",
"model_kwargs",
"json_mode",
"output_schema",
"model_name",
"openai_api_base",
@ -811,7 +819,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict)\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
},
"input_value": {
"advanced": false,
@ -833,6 +841,21 @@
"type": "str",
"value": ""
},
"json_mode": {
"advanced": true,
"display_name": "JSON Mode",
"dynamic": false,
"info": "If True, it will output JSON regardless of passing a schema.",
"list": false,
"name": "json_mode",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"max_tokens": {
"advanced": true,
"display_name": "Max Tokens",
@ -906,7 +929,7 @@
"dynamic": false,
"info": "The OpenAI API Key to use for the OpenAI model.",
"input_types": [],
"load_from_db": false,
"load_from_db": true,
"name": "openai_api_key",
"password": true,
"placeholder": "",
@ -914,7 +937,7 @@
"show": true,
"title_case": false,
"type": "str",
"value": ""
"value": "OPENAI_API_KEY"
},
"output_schema": {
"advanced": true,
@ -994,26 +1017,26 @@
}
}
},
"type": "OpenAIModel"
"type": "OpenAIModelComponent"
},
"dragging": false,
"height": 621,
"id": "OpenAIModel-O0AGC",
"id": "OpenAIModel-U2g5u",
"position": {
"x": 1227.3672858178775,
"y": 11.61201090144857
"x": 1249.1992451905348,
"y": 2.8792271523856243
},
"positionAbsolute": {
"x": 1227.3672858178775,
"y": 11.61201090144857
"x": 1249.1992451905348,
"y": 2.8792271523856243
},
"selected": false,
"selected": true,
"type": "genericNode",
"width": 384
},
{
"data": {
"id": "ChatOutput-efggd",
"id": "ChatOutput-yZjPO",
"node": {
"base_classes": [
"Message"
@ -1173,7 +1196,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatOutput-efggd",
"id": "ChatOutput-yZjPO",
"position": {
"x": 1831.1359796346408,
"y": 139.5174517327903
@ -1188,15 +1211,15 @@
}
],
"viewport": {
"x": 249.03047748371796,
"y": 251.71203687916693,
"x": 252.03047748371796,
"y": 253.71203687916693,
"zoom": 0.4580440916596844
}
},
"description": "This flow integrates PDF reading with a language model to answer document-specific questions. Ideal for small-scale texts, it facilitates direct queries with immediate insights.",
"endpoint_name": null,
"id": "4b4cbf9e-34fe-4613-a460-3b7af89b7788",
"id": "483d5200-b59b-4afa-a71f-52fcfcde8fca",
"is_component": false,
"last_tested_version": "1.0.0rc1",
"last_tested_version": "1.0.5",
"name": "Document QA"
}

View file

@ -2,10 +2,11 @@
"data": {
"edges": [
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "Memory",
"id": "Memory-uy2TA",
"id": "Memory-VIq7F",
"name": "messages_text",
"output_types": [
"Message"
@ -13,7 +14,7 @@
},
"targetHandle": {
"fieldName": "context",
"id": "Prompt-m9rUs",
"id": "Prompt-gEaWL",
"inputTypes": [
"Message",
"Text"
@ -21,17 +22,18 @@
"type": "str"
}
},
"id": "reactflow__edge-Memory-uy2TA{œdataTypeœ:œMemoryœ,œidœ:œMemory-uy2TAœ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-m9rUs{œfieldNameœ:œcontextœ,œidœ:œPrompt-m9rUsœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "Memory-uy2TA",
"sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-uy2TAœ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-m9rUs",
"targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-m9rUsœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-Memory-VIq7F{œdataTypeœ:œMemoryœ,œidœ:œMemory-VIq7Fœ,œnameœ:œmessages_textœ,œoutput_typesœ:[œMessageœ]}-Prompt-gEaWL{œfieldNameœ:œcontextœ,œidœ:œPrompt-gEaWLœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "Memory-VIq7F",
"sourceHandle": "{œdataTypeœ: œMemoryœ, œidœ: œMemory-VIq7Fœ, œnameœ: œmessages_textœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-gEaWL",
"targetHandle": "{œfieldNameœ: œcontextœ, œidœ: œPrompt-gEaWLœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "ChatInput",
"id": "ChatInput-hSTqh",
"id": "ChatInput-gIy9N",
"name": "message",
"output_types": [
"Message"
@ -39,7 +41,7 @@
},
"targetHandle": {
"fieldName": "user_message",
"id": "Prompt-m9rUs",
"id": "Prompt-gEaWL",
"inputTypes": [
"Message",
"Text"
@ -47,17 +49,18 @@
"type": "str"
}
},
"id": "reactflow__edge-ChatInput-hSTqh{œdataTypeœ:œChatInputœ,œidœ:œChatInput-hSTqhœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-m9rUs{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-m9rUsœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ChatInput-hSTqh",
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-hSTqhœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-m9rUs",
"targetHandle": "{œfieldNameœ: œuser_messageœ, œidœ: œPrompt-m9rUsœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-ChatInput-gIy9N{œdataTypeœ:œChatInputœ,œidœ:œChatInput-gIy9Nœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-Prompt-gEaWL{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-gEaWLœ,œinputTypesœ:[œMessageœ,œTextœ],œtypeœ:œstrœ}",
"source": "ChatInput-gIy9N",
"sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-gIy9Nœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}",
"target": "Prompt-gEaWL",
"targetHandle": "{œfieldNameœ: œuser_messageœ, œidœ: œPrompt-gEaWLœ, œinputTypesœ: [œMessageœ, œTextœ], œtypeœ: œstrœ}"
},
{
"className": "",
"data": {
"sourceHandle": {
"dataType": "Prompt",
"id": "Prompt-m9rUs",
"id": "Prompt-gEaWL",
"name": "prompt",
"output_types": [
"Message"
@ -65,24 +68,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "OpenAIModel-WmUtU",
"id": "OpenAIModel-uNcAU",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-Prompt-m9rUs{œdataTypeœ:œPromptœ,œidœ:œPrompt-m9rUsœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-WmUtU{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-WmUtUœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-m9rUs",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-m9rUsœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-WmUtU",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-WmUtUœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-Prompt-gEaWL{œdataTypeœ:œPromptœ,œidœ:œPrompt-gEaWLœ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-OpenAIModel-uNcAU{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-uNcAUœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "Prompt-gEaWL",
"sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-gEaWLœ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}",
"target": "OpenAIModel-uNcAU",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œOpenAIModel-uNcAUœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
},
{
"data": {
"sourceHandle": {
"dataType": "OpenAIModel",
"id": "OpenAIModel-WmUtU",
"dataType": "OpenAIModelComponent",
"id": "OpenAIModel-uNcAU",
"name": "text_output",
"output_types": [
"Message"
@ -90,24 +93,24 @@
},
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-LIvGN",
"id": "ChatOutput-KtSB9",
"inputTypes": [
"Message"
],
"type": "str"
}
},
"id": "reactflow__edge-OpenAIModel-WmUtU{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-WmUtUœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-LIvGN{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-LIvGNœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-WmUtU",
"sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-WmUtUœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-LIvGN",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-LIvGNœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
"id": "reactflow__edge-OpenAIModel-uNcAU{œdataTypeœ:œOpenAIModelComponentœ,œidœ:œOpenAIModel-uNcAUœ,œnameœ:œtext_outputœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-KtSB9{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-KtSB9œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}",
"source": "OpenAIModel-uNcAU",
"sourceHandle": "{œdataTypeœ: œOpenAIModelComponentœ, œidœ: œOpenAIModel-uNcAUœ, œnameœ: œtext_outputœ, œoutput_typesœ: [œMessageœ]}",
"target": "ChatOutput-KtSB9",
"targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-KtSB9œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}"
}
],
"nodes": [
{
"data": {
"id": "Memory-uy2TA",
"id": "Memory-VIq7F",
"node": {
"base_classes": [
"Data",
@ -296,7 +299,7 @@
},
"dragging": false,
"height": 266,
"id": "Memory-uy2TA",
"id": "Memory-VIq7F",
"position": {
"x": 1264.7588980556088,
"y": 506.6868269980502
@ -313,7 +316,7 @@
"data": {
"description": "Create a prompt template with dynamic variables.",
"display_name": "Prompt",
"id": "Prompt-m9rUs",
"id": "Prompt-gEaWL",
"node": {
"base_classes": [
"Message"
@ -446,7 +449,7 @@
},
"dragging": false,
"height": 515,
"id": "Prompt-m9rUs",
"id": "Prompt-gEaWL",
"position": {
"x": 1880.8227904110583,
"y": 625.8049209882275
@ -461,7 +464,7 @@
},
{
"data": {
"id": "ChatInput-hSTqh",
"id": "ChatInput-gIy9N",
"node": {
"base_classes": [
"Message"
@ -644,7 +647,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatInput-hSTqh",
"id": "ChatInput-gIy9N",
"position": {
"x": 1275.9262193671882,
"y": 836.1228056896347
@ -659,7 +662,10 @@
},
{
"data": {
"id": "OpenAIModel-WmUtU",
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"edited": false,
"id": "OpenAIModel-uNcAU",
"node": {
"base_classes": [
"LanguageModel",
@ -671,11 +677,12 @@
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI",
"documentation": "",
"edited": false,
"edited": true,
"field_order": [
"input_value",
"max_tokens",
"model_kwargs",
"json_mode",
"output_schema",
"model_name",
"openai_api_base",
@ -732,7 +739,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict)\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
"value": "import operator\nfrom functools import reduce\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import LanguageModel\nfrom langflow.inputs import (\n BoolInput,\n DictInput,\n DropdownInput,\n FloatInput,\n IntInput,\n MessageInput,\n SecretStrInput,\n StrInput,\n)\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n inputs = [\n MessageInput(name=\"input_value\", display_name=\"Input\"),\n IntInput(\n name=\"max_tokens\",\n display_name=\"Max Tokens\",\n advanced=True,\n info=\"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n ),\n DictInput(name=\"model_kwargs\", display_name=\"Model Kwargs\", advanced=True),\n BoolInput(\n name=\"json_mode\",\n display_name=\"JSON Mode\",\n advanced=True,\n info=\"If True, it will output JSON regardless of passing a schema.\",\n ),\n DictInput(\n name=\"output_schema\",\n is_list=True,\n display_name=\"Schema\",\n advanced=True,\n info=\"The schema for the Output of the model. You must pass the word JSON in the prompt. If left blank, JSON mode will be disabled.\",\n ),\n DropdownInput(\n name=\"model_name\", display_name=\"Model Name\", advanced=False, options=MODEL_NAMES, value=MODEL_NAMES[0]\n ),\n StrInput(\n name=\"openai_api_base\",\n display_name=\"OpenAI API Base\",\n advanced=True,\n info=\"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1. You can change this to use other APIs like JinaChat, LocalAI and Prem.\",\n ),\n SecretStrInput(\n name=\"openai_api_key\",\n display_name=\"OpenAI API Key\",\n info=\"The OpenAI API Key to use for the OpenAI model.\",\n advanced=False,\n value=\"OPENAI_API_KEY\",\n ),\n FloatInput(name=\"temperature\", display_name=\"Temperature\", value=0.1),\n BoolInput(name=\"stream\", display_name=\"Stream\", info=STREAM_INFO_TEXT, advanced=True),\n StrInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"System message to pass to the model.\",\n advanced=True,\n ),\n IntInput(\n name=\"seed\",\n display_name=\"Seed\",\n info=\"The seed controls the reproducibility of the job.\",\n advanced=True,\n value=1,\n ),\n ]\n\n def build_model(self) -> LanguageModel:\n # self.output_schea is a list of dictionaries\n # let's convert it to a dictionary\n output_schema_dict: dict[str, str] = reduce(operator.ior, self.output_schema or {}, {})\n openai_api_key = self.openai_api_key\n temperature = self.temperature\n model_name: str = self.model_name\n max_tokens = self.max_tokens\n model_kwargs = self.model_kwargs or {}\n openai_api_base = self.openai_api_base or \"https://api.openai.com/v1\"\n json_mode = bool(output_schema_dict) or self.json_mode\n seed = self.seed\n model_kwargs[\"seed\"] = seed\n\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature or 0.1,\n )\n if json_mode:\n if output_schema_dict:\n output = output.with_structured_output(schema=output_schema_dict, method=\"json_mode\") # type: ignore\n else:\n output = output.bind(response_format={\"type\": \"json_object\"}) # type: ignore\n\n return output\n\n def _get_exception_message(self, e: Exception):\n \"\"\"\n Get a message from an OpenAI exception.\n\n Args:\n exception (Exception): The exception to get the message from.\n\n Returns:\n str: The message from the exception.\n \"\"\"\n\n try:\n from openai import BadRequestError\n except ImportError:\n return\n if isinstance(e, BadRequestError):\n message = e.body.get(\"message\") # type: ignore\n if message:\n return message\n return\n"
},
"input_value": {
"advanced": false,
@ -754,6 +761,21 @@
"type": "str",
"value": ""
},
"json_mode": {
"advanced": true,
"display_name": "JSON Mode",
"dynamic": false,
"info": "If True, it will output JSON regardless of passing a schema.",
"list": false,
"name": "json_mode",
"placeholder": "",
"required": false,
"show": true,
"title_case": false,
"trace_as_metadata": true,
"type": "bool",
"value": false
},
"max_tokens": {
"advanced": true,
"display_name": "Max Tokens",
@ -827,7 +849,7 @@
"dynamic": false,
"info": "The OpenAI API Key to use for the OpenAI model.",
"input_types": [],
"load_from_db": false,
"load_from_db": true,
"name": "openai_api_key",
"password": true,
"placeholder": "",
@ -835,7 +857,7 @@
"show": true,
"title_case": false,
"type": "str",
"value": ""
"value": "OPENAI_API_KEY"
},
"output_schema": {
"advanced": true,
@ -915,11 +937,11 @@
}
}
},
"type": "OpenAIModel"
"type": "OpenAIModelComponent"
},
"dragging": false,
"height": 621,
"id": "OpenAIModel-WmUtU",
"id": "OpenAIModel-uNcAU",
"position": {
"x": 2428.0215346784357,
"y": 569.9683144303319
@ -934,7 +956,7 @@
},
{
"data": {
"id": "ChatOutput-LIvGN",
"id": "ChatOutput-KtSB9",
"node": {
"base_classes": [
"Message"
@ -1094,7 +1116,7 @@
},
"dragging": false,
"height": 308,
"id": "ChatOutput-LIvGN",
"id": "ChatOutput-KtSB9",
"position": {
"x": 2988.248820475989,
"y": 705.837390387878
@ -1116,8 +1138,8 @@
},
"description": "This project can be used as a starting point for building a Chat experience with user specific memory. You can set a different Session ID to start a new message history.",
"endpoint_name": null,
"id": "2a47bc35-69ca-4d8b-9895-2a7fab222b9f",
"id": "16c029a0-0d89-4c36-8a8c-e5410206df38",
"is_component": false,
"last_tested_version": "1.0.0rc1",
"last_tested_version": "1.0.5",
"name": "Memory Chatbot"
}

View file

@ -1,20 +1,24 @@
import warnings
from typing import List, Optional
from uuid import UUID
from loguru import logger
from sqlalchemy import delete
from sqlmodel import Session, col, select
from langflow.schema.message import Message
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import MessageModel
from langflow.services.database.models.message.model import MessageRead, MessageTable
from langflow.services.database.utils import migrate_messages_from_monitor_service_to_database
from langflow.services.deps import session_scope
def get_messages(
sender: Optional[str] = None,
sender_name: Optional[str] = None,
session_id: Optional[str] = None,
order_by: Optional[str] = "timestamp",
order: Optional[str] = "DESC",
limit: Optional[int] = None,
sender: str | None = None,
sender_name: str | None = None,
session_id: str | None = None,
order_by: str | None = "timestamp",
order: str | None = "DESC",
flow_id: UUID | None = None,
limit: int | None = None,
):
"""
Retrieves messages from the monitor service based on the provided filters.
@ -29,42 +33,38 @@ def get_messages(
Returns:
List[Data]: A list of Data objects representing the retrieved messages.
"""
monitor_service = get_monitor_service()
messages_df = monitor_service.get_messages(
sender=sender,
sender_name=sender_name,
session_id=session_id,
order_by=order_by,
limit=limit,
order=order,
)
with session_scope() as session:
migrate_messages_from_monitor_service_to_database(session)
messages_read: list[Message] = []
with session_scope() as session:
stmt = select(MessageTable)
if sender:
stmt = stmt.where(MessageTable.sender == sender)
if sender_name:
stmt = stmt.where(MessageTable.sender_name == sender_name)
if session_id:
stmt = stmt.where(MessageTable.session_id == session_id)
if flow_id:
stmt = stmt.where(MessageTable.flow_id == flow_id)
if order_by:
if order == "DESC":
col = getattr(MessageTable, order_by).desc()
else:
col = getattr(MessageTable, order_by).asc()
stmt = stmt.order_by(col)
if limit:
stmt = stmt.limit(limit)
messages = session.exec(stmt)
messages_read = [Message(**d.model_dump()) for d in messages]
messages: list[Message] = []
# messages_df has a timestamp
# it gets the last 5 messages, for example
# but now they are ordered from most recent to least recent
# so we need to reverse the order
messages_df = messages_df[::-1] if order == "DESC" else messages_df
for row in messages_df.itertuples():
msg = Message(
text=row.text,
sender=row.sender,
session_id=row.session_id,
sender_name=row.sender_name,
timestamp=row.timestamp,
)
messages.append(msg)
return messages
return messages_read
def add_messages(messages: Message | list[Message], flow_id: Optional[str] = None):
def add_messages(messages: Message | list[Message], flow_id: str | None = None):
"""
Add a message to the monitor service.
"""
try:
monitor_service = get_monitor_service()
if not isinstance(messages, list):
messages = [messages]
@ -72,25 +72,29 @@ def add_messages(messages: Message | list[Message], flow_id: Optional[str] = Non
types = ", ".join([str(type(message)) for message in messages])
raise ValueError(f"The messages must be instances of Message. Found: {types}")
messages_models: list[MessageModel] = []
messages_models: list[MessageTable] = []
for msg in messages:
if not msg.timestamp:
msg.timestamp = monitor_service.get_timestamp()
messages_models.append(MessageModel.from_message(msg, flow_id=flow_id))
for message_model in messages_models:
try:
monitor_service.add_message(message_model)
except Exception as e:
logger.error(f"Error adding message to monitor service: {e}")
logger.exception(e)
raise e
return messages_models
messages_models.append(MessageTable.from_message(msg, flow_id=flow_id))
with session_scope() as session:
messages_models = add_messagetables(messages_models, session)
return [Message(**message.model_dump()) for message in messages_models]
except Exception as e:
logger.exception(e)
raise e
def add_messagetables(messages: list[MessageTable], session: Session):
for message in messages:
try:
session.add(message)
session.commit()
session.refresh(message)
except Exception as e:
logger.exception(e)
raise e
return [MessageRead.model_validate(message, from_attributes=True) for message in messages]
def delete_messages(session_id: str):
"""
Delete messages from the monitor service based on the provided session ID.
@ -98,14 +102,19 @@ def delete_messages(session_id: str):
Args:
session_id (str): The session ID associated with the messages to delete.
"""
monitor_service = get_monitor_service()
monitor_service.delete_messages_session(session_id)
with session_scope() as session:
session.exec(
delete(MessageTable)
.where(col(MessageTable.session_id) == session_id)
.execution_options(synchronize_session="fetch")
)
session.commit()
def store_message(
message: Message,
flow_id: Optional[str] = None,
) -> List[Message]:
flow_id: str | None = None,
) -> list[Message]:
"""
Stores a message in the memory.

View file

@ -1,5 +1,6 @@
from datetime import datetime, timezone
from typing import Annotated, Any, AsyncIterator, Iterator, List, Optional
from uuid import UUID
from fastapi.encoders import jsonable_encoder
from langchain_core.load import load
@ -31,7 +32,20 @@ class Message(Data):
timestamp: Annotated[str, BeforeValidator(_timestamp_to_str)] = Field(
default=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
)
flow_id: Optional[str] = None
flow_id: Optional[str | UUID] = None
@field_validator("flow_id", mode="before")
@classmethod
def validate_flow_id(cls, value):
if isinstance(value, UUID):
value = str(value)
return value
@field_serializer("flow_id")
def serialize_flow_id(value):
if isinstance(value, str):
return UUID(value)
return value
@field_validator("files", mode="before")
@classmethod

View file

@ -1,3 +1,5 @@
import base64
import random
import warnings
from datetime import datetime, timedelta, timezone
from typing import Annotated, Coroutine, Optional, Union
@ -330,17 +332,25 @@ def authenticate_user(username: str, password: str, db: Session = Depends(get_se
return user if verify_password(password, user.password) else None
def add_padding(s):
# Calculate the number of padding characters needed
padding_needed = 4 - len(s) % 4
return s + "=" * padding_needed
def ensure_valid_key(s: str) -> bytes:
# If the key is too short, we'll use it as a seed to generate a valid key
if len(s) < 32:
# Use the input as a seed for the random number generator
random.seed(s)
# Generate 32 random bytes
key = bytes(random.getrandbits(8) for _ in range(32))
else:
# If the key is long enough, use the first 32 bytes
key = s[:32].encode()
# Ensure the key is URL-safe base64-encoded
return base64.urlsafe_b64encode(key)
def get_fernet(settings_service=Depends(get_settings_service)):
SECRET_KEY = settings_service.auth_settings.SECRET_KEY.get_secret_value()
# It's important that your secret key is 32 url-safe base64-encoded byte
padded_secret_key = add_padding(SECRET_KEY)
fernet = Fernet(padded_secret_key)
valid_key = ensure_valid_key(SECRET_KEY)
fernet = Fernet(valid_key)
return fernet

View file

@ -1,7 +1,8 @@
from .api_key import ApiKey
from .flow import Flow
from .folder import Folder
from .message import MessageTable
from .user import User
from .variable import Variable
__all__ = ["Flow", "User", "ApiKey", "Variable", "Folder"]
__all__ = ["Flow", "User", "ApiKey", "Variable", "Folder", "MessageTable"]

View file

@ -3,7 +3,7 @@
import re
import warnings
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Dict, List, Optional
from uuid import UUID, uuid4
import emoji
@ -17,6 +17,7 @@ from langflow.schema import Data
if TYPE_CHECKING:
from langflow.services.database.models.folder import Folder
from langflow.services.database.models.message import MessageTable
from langflow.services.database.models.user import User
@ -141,6 +142,7 @@ class Flow(FlowBase, table=True):
user: "User" = Relationship(back_populates="flows")
folder_id: Optional[UUID] = Field(default=None, foreign_key="folder.id", nullable=True, index=True)
folder: Optional["Folder"] = Relationship(back_populates="flows")
messages: List["MessageTable"] = Relationship(back_populates="flow")
def to_data(self):
serialized = self.model_dump()

View file

@ -0,0 +1,3 @@
from .model import MessageTable, MessageCreate, MessageRead, MessageUpdate
__all__ = ["MessageTable", "MessageCreate", "MessageRead", "MessageUpdate"]

View file

@ -0,0 +1,85 @@
from datetime import datetime, timezone
from typing import TYPE_CHECKING, List, Optional
from uuid import UUID, uuid4
from pydantic import field_validator
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
if TYPE_CHECKING:
from langflow.schema.message import Message
from langflow.services.database.models.flow.model import Flow
class MessageBase(SQLModel):
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
sender: str
sender_name: str
session_id: str
text: str
files: list[str] = Field(default_factory=list)
@field_validator("files", mode="before")
@classmethod
def validate_files(cls, value):
if not value:
value = []
return value
@classmethod
def from_message(cls, message: "Message", flow_id: str | UUID | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
if isinstance(message.timestamp, str):
timestamp = datetime.fromisoformat(message.timestamp)
else:
timestamp = message.timestamp
if not flow_id and message.flow_id:
flow_id = message.flow_id
return cls(
sender=message.sender,
sender_name=message.sender_name,
text=message.text,
session_id=message.session_id,
files=message.files or [],
timestamp=timestamp,
flow_id=flow_id,
)
class MessageTable(MessageBase, table=True):
__tablename__ = "message"
id: UUID = Field(default_factory=uuid4, primary_key=True)
flow_id: Optional[UUID] = Field(default=None, foreign_key="flow.id")
flow: "Flow" = Relationship(back_populates="messages")
files: List[str] = Field(sa_column=Column(JSON))
@field_validator("flow_id", mode="before")
@classmethod
def validate_flow_id(cls, value):
if value is None:
return value
if isinstance(value, str):
value = UUID(value)
return value
# Needed for Column(JSON)
class Config:
arbitrary_types_allowed = True
class MessageRead(MessageBase):
id: UUID
flow_id: Optional[UUID] = Field()
class MessageCreate(MessageBase):
pass
class MessageUpdate(SQLModel):
text: Optional[str] = None
sender: Optional[str] = None
sender_name: Optional[str] = None
session_id: Optional[str] = None
files: Optional[list[str]] = None

View file

@ -6,22 +6,24 @@ from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import command, util
from alembic.config import Config
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
from loguru import logger
from sqlalchemy import event, inspect
from sqlalchemy.engine import Engine
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults, migrate_messages_from_monitor_service_to_database
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
if TYPE_CHECKING:
from langflow.services.settings.service import SettingsService
from sqlalchemy.engine import Engine
from langflow.services.settings.service import SettingsService
class DatabaseService(Service):
name = "database_service"
@ -205,6 +207,10 @@ class DatabaseService(Service):
logger.error(f"AutogenerateDiffsDetected: {exc}")
if not fix:
raise RuntimeError(f"There's a mismatch between the models and the database.\n{exc}")
try:
migrate_messages_from_monitor_service_to_database(session)
except Exception as exc:
logger.error(f"Error migrating messages from monitor service to database: {exc}")
if fix:
self.try_downgrade_upgrade_until_success(alembic_cfg)

View file

@ -4,11 +4,82 @@ from typing import TYPE_CHECKING
from alembic.util.exc import CommandError
from loguru import logger
from sqlmodel import Session, text
from sqlmodel import Session, select, text
from langflow.services.deps import get_monitor_service
if TYPE_CHECKING:
from langflow.services.database.service import DatabaseService
from typing import Dict, List
def migrate_messages_from_monitor_service_to_database(session: Session) -> bool:
from langflow.schema.message import Message
from langflow.services.database.models.message import MessageTable
try:
monitor_service = get_monitor_service()
messages_df = monitor_service.get_messages()
except Exception as e:
logger.error(f"Error retrieving messages from monitor service: {e}")
return False
if messages_df.empty:
logger.info("No messages to migrate.")
return True
original_messages: List[Dict] = messages_df.to_dict(orient="records")
db_messages = session.exec(select(MessageTable)).all()
db_messages = [msg[0] for msg in db_messages] # type: ignore
db_msg_dict = {(msg.text, msg.timestamp.isoformat(), str(msg.flow_id), msg.session_id): msg for msg in db_messages}
# Filter out messages that already exist in the database
original_messages_filtered = []
for message in original_messages:
key = (message["text"], message["timestamp"].isoformat(), str(message["flow_id"]), message["session_id"])
if key not in db_msg_dict:
original_messages_filtered.append(message)
if not original_messages_filtered:
logger.info("No messages to migrate.")
return True
try:
# Bulk insert messages
session.bulk_insert_mappings(
MessageTable, # type: ignore
[MessageTable.from_message(Message(**msg)).model_dump() for msg in original_messages_filtered],
)
session.commit()
except Exception as e:
logger.error(f"Error during message insertion: {str(e)}")
session.rollback()
return False
# Create a dictionary for faster lookup
all_ok = True
for orig_msg in original_messages_filtered:
key = (orig_msg["text"], orig_msg["timestamp"].isoformat(), str(orig_msg["flow_id"]), orig_msg["session_id"])
matching_db_msg = db_msg_dict.get(key)
if matching_db_msg is None:
logger.warning(f"Message not found in database: {orig_msg}")
all_ok = False
else:
# Validate other fields
if any(getattr(matching_db_msg, k) != v for k, v in orig_msg.items() if k != "index"):
logger.warning(f"Message mismatch in database: {orig_msg}")
all_ok = False
if all_ok:
messages_ids = [message["index"] for message in original_messages]
monitor_service.delete_messages(messages_ids)
logger.info("Migration completed successfully. Original messages deleted.")
else:
logger.warning("Migration completed with errors. Original messages not deleted.")
return all_ok
def initialize_database(fix_migration: bool = False):
logger.debug("Initializing database")

View file

@ -1,6 +1,7 @@
import json
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any
from uuid import UUID
from pydantic import BaseModel, Field, field_serializer, field_validator
@ -27,15 +28,15 @@ class DefaultModel(BaseModel):
class TransactionModel(DefaultModel):
index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
index: int | None = Field(default=None)
timestamp: datetime | None = Field(default_factory=datetime.now, alias="timestamp")
vertex_id: str
target_id: str | None = None
inputs: dict
outputs: Optional[dict] = None
outputs: dict | None = None
status: str
error: Optional[str] = None
flow_id: Optional[str] = Field(default=None, alias="flow_id")
error: str | None = None
flow_id: str | None = Field(default=None, alias="flow_id")
# validate target_args in case it is a JSON
@field_validator("outputs", "inputs", mode="before")
@ -52,16 +53,16 @@ class TransactionModel(DefaultModel):
class TransactionModelResponse(DefaultModel):
index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
index: int | None = Field(default=None)
timestamp: datetime | None = Field(default_factory=datetime.now, alias="timestamp")
vertex_id: str
inputs: dict
outputs: Optional[dict] = None
outputs: dict | None = None
status: str
error: Optional[str] = None
flow_id: Optional[str] = Field(default=None, alias="flow_id")
source: Optional[str] = None
target: Optional[str] = None
error: str | None = None
flow_id: str | None = Field(default=None, alias="flow_id")
source: str | None = None
target: str | None = None
# validate target_args in case it is a JSON
@field_validator("outputs", "inputs", mode="before")
@ -80,9 +81,9 @@ class TransactionModelResponse(DefaultModel):
return v
class MessageModel(DefaultModel):
index: Optional[int] = Field(default=None)
flow_id: Optional[str] = Field(default=None, alias="flow_id")
class DuckDbMessageModel(DefaultModel):
index: int | None = Field(default=None, alias="index")
flow_id: str | None = Field(default=None, alias="flow_id")
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
sender: str
sender_name: str
@ -111,7 +112,53 @@ class MessageModel(DefaultModel):
return v
@classmethod
def from_message(cls, message: Message, flow_id: Optional[str] = None):
def from_message(cls, message: Message, flow_id: str | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
return cls(
sender=message.sender,
sender_name=message.sender_name,
text=message.text,
session_id=message.session_id,
files=message.files or [],
timestamp=message.timestamp,
flow_id=flow_id,
)
class MessageModel(DefaultModel):
id: str | UUID | None = Field(default=None)
flow_id: UUID | None = Field(default=None)
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
sender: str
sender_name: str
session_id: str
text: str
files: list[str] = []
@field_validator("files", mode="before")
@classmethod
def validate_files(cls, v):
if isinstance(v, str):
v = json.loads(v)
return v
@field_serializer("timestamp")
@classmethod
def serialize_timestamp(cls, v):
v = v.replace(microsecond=0)
return v.strftime("%Y-%m-%d %H:%M:%S")
@field_serializer("files")
@classmethod
def serialize_files(cls, v):
if isinstance(v, list):
return json.dumps(v)
return v
@classmethod
def from_message(cls, message: Message, flow_id: str | None = None):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
@ -127,16 +174,7 @@ class MessageModel(DefaultModel):
class MessageModelResponse(MessageModel):
index: Optional[int] = Field(default=None)
@field_validator("index", mode="before")
def validate_id(cls, v):
if isinstance(v, float):
try:
return int(v)
except ValueError:
return None
return v
pass
class MessageModelRequest(MessageModel):
@ -147,8 +185,8 @@ class MessageModelRequest(MessageModel):
class VertexBuildModel(DefaultModel):
index: Optional[int] = Field(default=None, alias="index", exclude=True)
id: Optional[str] = Field(default=None, alias="id")
index: int | None = Field(default=None, alias="index", exclude=True)
id: str | None = Field(default=None, alias="id")
flow_id: str
valid: bool
params: Any

View file

@ -1,30 +1,31 @@
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING, List, Optional, Union
from typing import TYPE_CHECKING, Union
import duckdb
from langflow.services.base import Service
from langflow.services.monitor.utils import add_row_to_table, drop_and_create_table_if_schema_mismatch
from loguru import logger
from platformdirs import user_cache_dir
from langflow.services.base import Service
from langflow.services.monitor.utils import add_row_to_table, drop_and_create_table_if_schema_mismatch
if TYPE_CHECKING:
from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel
from langflow.services.settings.service import SettingsService
from langflow.services.monitor.schema import MessageModel, TransactionModel, VertexBuildModel
class MonitorService(Service):
name = "monitor_service"
def __init__(self, settings_service: "SettingsService"):
from langflow.services.monitor.schema import MessageModel, TransactionModel, VertexBuildModel
from langflow.services.monitor.schema import DuckDbMessageModel, TransactionModel, VertexBuildModel
self.settings_service = settings_service
self.base_cache_dir = Path(user_cache_dir("langflow"))
self.db_path = self.base_cache_dir / "monitor.duckdb"
self.table_map: dict[str, type[TransactionModel | MessageModel | VertexBuildModel]] = {
self.table_map: dict[str, type[TransactionModel | DuckDbMessageModel | VertexBuildModel]] = {
"transactions": TransactionModel,
"messages": MessageModel,
"messages": DuckDbMessageModel,
"vertex_builds": VertexBuildModel,
}
@ -47,7 +48,7 @@ class MonitorService(Service):
def add_row(
self,
table_name: str,
data: Union[dict, "TransactionModel", "MessageModel", "VertexBuildModel"],
data: Union[dict, "TransactionModel", "DuckDbMessageModel", "VertexBuildModel"],
):
# Make sure the model passed matches the table
@ -67,80 +68,15 @@ class MonitorService(Service):
def get_timestamp():
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def get_vertex_builds(
self,
flow_id: Optional[str] = None,
vertex_id: Optional[str] = None,
valid: Optional[bool] = None,
order_by: Optional[str] = "timestamp",
):
query = "SELECT id, index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
conditions = []
if flow_id:
conditions.append(f"flow_id = '{flow_id}'")
if vertex_id:
conditions.append(f"id = '{vertex_id}'")
if valid is not None: # Check for None because valid is a boolean
valid_str = "true" if valid else "false"
conditions.append(f"valid = {valid_str}")
if conditions:
query += " WHERE " + " AND ".join(conditions)
if order_by:
query += f" ORDER BY {order_by}"
with duckdb.connect(str(self.db_path), read_only=True) as conn:
df = conn.execute(query).df()
return df.to_dict(orient="records")
def delete_vertex_builds(self, flow_id: Optional[str] = None):
query = "DELETE FROM vertex_builds"
if flow_id:
query += f" WHERE flow_id = '{flow_id}'"
with duckdb.connect(str(self.db_path), read_only=False) as conn:
conn.execute(query)
def delete_messages_session(self, session_id: str):
query = f"DELETE FROM messages WHERE session_id = '{session_id}'"
return self.exec_query(query, read_only=False)
def delete_messages(self, message_ids: Union[List[int], str]):
if isinstance(message_ids, list):
# If message_ids is a list, join the string representations of the integers
ids_str = ",".join(map(str, message_ids))
elif isinstance(message_ids, str):
# If message_ids is already a string, use it directly
ids_str = message_ids
else:
raise ValueError("message_ids must be a list of integers or a string")
query = f"DELETE FROM messages WHERE index IN ({ids_str})"
return self.exec_query(query, read_only=False)
def update_message(self, message_id: str, **kwargs):
query = (
f"""UPDATE messages SET {', '.join(f"{k} = '{v}'" for k, v in kwargs.items())} WHERE index = {message_id}"""
)
return self.exec_query(query, read_only=False)
def add_message(self, message: "MessageModel"):
self.add_row("messages", message)
def get_messages(
self,
flow_id: Optional[str] = None,
sender: Optional[str] = None,
sender_name: Optional[str] = None,
session_id: Optional[str] = None,
order_by: Optional[str] = "timestamp",
order: Optional[str] = "DESC",
limit: Optional[int] = None,
flow_id: str | None = None,
sender: str | None = None,
sender_name: str | None = None,
session_id: str | None = None,
order_by: str | None = "timestamp",
order: str | None = "DESC",
limit: int | None = None,
):
query = "SELECT index, flow_id, sender_name, sender, session_id, text, files, timestamp FROM messages"
conditions = []
@ -168,13 +104,75 @@ class MonitorService(Service):
return df
def get_vertex_builds(
self,
flow_id: str | None = None,
vertex_id: str | None = None,
valid: bool | None = None,
order_by: str | None = "timestamp",
):
query = "SELECT id, index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
conditions = []
if flow_id:
conditions.append(f"flow_id = '{flow_id}'")
if vertex_id:
conditions.append(f"id = '{vertex_id}'")
if valid is not None: # Check for None because valid is a boolean
valid_str = "true" if valid else "false"
conditions.append(f"valid = {valid_str}")
if conditions:
query += " WHERE " + " AND ".join(conditions)
if order_by:
query += f" ORDER BY {order_by}"
with duckdb.connect(str(self.db_path), read_only=True) as conn:
df = conn.execute(query).df()
return df.to_dict(orient="records")
def delete_vertex_builds(self, flow_id: str | None = None):
query = "DELETE FROM vertex_builds"
if flow_id:
query += f" WHERE flow_id = '{flow_id}'"
with duckdb.connect(str(self.db_path), read_only=False) as conn:
conn.execute(query)
def delete_messages_session(self, session_id: str):
query = f"DELETE FROM messages WHERE session_id = '{session_id}'"
return self.exec_query(query, read_only=False)
def delete_messages(self, message_ids: list[int] | str):
if isinstance(message_ids, list):
# If message_ids is a list, join the string representations of the integers
ids_str = ",".join(map(str, message_ids))
elif isinstance(message_ids, str):
# If message_ids is already a string, use it directly
ids_str = message_ids
else:
raise ValueError("message_ids must be a list of integers or a string")
query = f"DELETE FROM messages WHERE index IN ({ids_str})"
return self.exec_query(query, read_only=False)
def update_message(self, message_id: str, **kwargs):
query = (
f"""UPDATE messages SET {', '.join(f"{k} = '{v}'" for k, v in kwargs.items())} WHERE index = {message_id}"""
)
return self.exec_query(query, read_only=False)
def get_transactions(
self,
source: Optional[str] = None,
target: Optional[str] = None,
status: Optional[str] = None,
order_by: Optional[str] = "timestamp",
flow_id: Optional[str] = None,
source: str | None = None,
target: str | None = None,
status: str | None = None,
order_by: str | None = "timestamp",
flow_id: str | None = None,
):
query = (
"SELECT index,flow_id, status, error, timestamp, vertex_id, inputs, outputs, target_id FROM transactions"

View file

@ -0,0 +1,33 @@
from abc import ABC, abstractmethod
from typing import Any, Dict
from uuid import UUID
class BaseTracer(ABC):
@abstractmethod
def __init__(self, trace_name: str, trace_type: str, project_name: str, trace_id: UUID):
raise NotImplementedError
@abstractmethod
def ready(self):
raise NotImplementedError
@abstractmethod
def add_trace(
self, trace_name: str, trace_type: str, inputs: Dict[str, Any], metadata: Dict[str, Any] | None = None
):
raise NotImplementedError
@abstractmethod
def end_trace(self, trace_name: str, outputs: Dict[str, Any] | None = None, error: str | None = None):
raise NotImplementedError
@abstractmethod
def end(
self,
inputs: dict[str, Any],
outputs: Dict[str, Any],
error: str | None = None,
metadata: dict[str, Any] | None = None,
):
raise NotImplementedError

View file

@ -12,6 +12,7 @@ from loguru import logger
from langflow.schema.data import Data
from langflow.services.base import Service
from langflow.services.tracing.base import BaseTracer
from langflow.services.tracing.schema import Log
if TYPE_CHECKING:
@ -180,7 +181,7 @@ class TracingService(Service):
self.outputs_metadata[trace_name] |= output_metadata or {}
class LangSmithTracer:
class LangSmithTracer(BaseTracer):
def __init__(self, trace_name: str, trace_type: str, project_name: str, trace_id: UUID):
from langsmith.run_trees import RunTree
@ -292,7 +293,7 @@ class LangSmithTracer:
inputs: dict[str, Any],
outputs: Dict[str, Any],
error: str | None = None,
metadata: Optional[dict[str, Any]] = None,
metadata: dict[str, Any] | None = None,
):
self._run_tree.add_metadata({"inputs": inputs, "metadata": metadata or {}})
self._run_tree.end(outputs=outputs, error=error)

View file

@ -0,0 +1,34 @@
from typing import Any, Dict
from langflow.schema.data import Data
def convert_to_langchain_type(value):
from langflow.schema.message import Message
if isinstance(value, dict):
for key, _value in value.copy().items():
_value = convert_to_langchain_type(_value)
value[key] = _value
elif isinstance(value, list):
value = [convert_to_langchain_type(v) for v in value]
elif isinstance(value, Message):
if "prompt" in value:
value = value.load_lc_prompt()
elif value.sender:
value = value.to_lc_message()
else:
value = value.to_lc_document()
elif isinstance(value, Data):
if "text" in value.data:
value = value.to_lc_document()
else:
value = value.data
return value
def convert_to_langchain_types(io_dict: Dict[str, Any]):
converted = {}
for key, value in io_dict.items():
converted[key] = convert_to_langchain_type(value)
return converted

View file

@ -1,5 +1,6 @@
import logging
import os
import sys
from pathlib import Path
from typing import Optional
@ -11,7 +12,7 @@ from rich.logging import RichHandler
VALID_LOG_LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
def serialize(record):
def serialize_log(record):
subset = {
"timestamp": record["time"].timestamp(),
"message": record["message"],
@ -22,54 +23,67 @@ def serialize(record):
def patching(record):
record["extra"]["serialized"] = serialize(record)
record["extra"]["serialized"] = serialize_log(record)
def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None, disable: Optional[bool] = False):
def configure(
log_level: Optional[str] = None,
log_file: Optional[Path] = None,
disable: Optional[bool] = False,
log_env: Optional[str] = None,
):
if disable and log_level is None and log_file is None:
logger.disable("langflow")
if os.getenv("LANGFLOW_LOG_LEVEL", "").upper() in VALID_LOG_LEVELS and log_level is None:
log_level = os.getenv("LANGFLOW_LOG_LEVEL")
if log_level is None:
log_level = "ERROR"
# Human-readable
log_format = (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> - <level>"
"{level: <8}</level> - {module} - <level>{message}</level>"
)
# log_format = log_format_dev if log_level.upper() == "DEBUG" else log_format_prod
if log_env is None:
log_env = os.getenv("LANGFLOW_LOG_ENV", "")
logger.remove() # Remove default handlers
logger.patch(patching)
# Configure loguru to use RichHandler
logger.configure(
handlers=[
{
"sink": RichHandler(rich_tracebacks=True, markup=True),
"format": log_format,
"level": log_level.upper(),
}
]
)
if not log_file:
cache_dir = Path(user_cache_dir("langflow"))
logger.debug(f"Cache directory: {cache_dir}")
log_file = cache_dir / "langflow.log"
logger.debug(f"Log file: {log_file}")
try:
log_file = Path(log_file)
log_file.parent.mkdir(parents=True, exist_ok=True)
logger.add(
sink=str(log_file),
level=log_level.upper(),
format=log_format,
rotation="10 MB", # Log rotation based on file size
serialize=True,
if log_env.lower() == "container" or log_env.lower() == "container_json":
logger.add(sys.stdout, format="{message}", serialize=True)
elif log_env.lower() == "container_csv":
logger.add(sys.stdout, format="{time:YYYY-MM-DD HH:mm:ss.SSS} {level} {file} {line} {function} {message}")
else:
# Human-readable
log_format = (
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> - <level>"
"{level: <8}</level> - {module} - <level>{message}</level>"
)
except Exception as exc:
logger.error(f"Error setting up log file: {exc}")
# Configure loguru to use RichHandler
logger.configure(
handlers=[
{
"sink": RichHandler(rich_tracebacks=True, markup=True),
"format": log_format,
"level": log_level.upper(),
}
]
)
if not log_file:
cache_dir = Path(user_cache_dir("langflow"))
logger.debug(f"Cache directory: {cache_dir}")
log_file = cache_dir / "langflow.log"
logger.debug(f"Log file: {log_file}")
try:
log_file = Path(log_file)
log_file.parent.mkdir(parents=True, exist_ok=True)
logger.add(
sink=str(log_file),
level=log_level.upper(),
format=log_format,
rotation="10 MB", # Log rotation based on file size
serialize=True,
)
except Exception as exc:
logger.error(f"Error setting up log file: {exc}")
logger.debug(f"Logger set up with log level: {log_level}")

View file

@ -112,13 +112,13 @@ frozenlist = ">=1.1.0"
[[package]]
name = "alembic"
version = "1.13.1"
version = "1.13.2"
description = "A database migration tool for SQLAlchemy."
optional = false
python-versions = ">=3.8"
files = [
{file = "alembic-1.13.1-py3-none-any.whl", hash = "sha256:2edcc97bed0bd3272611ce3a98d98279e9c209e7186e43e75bbb1b2bdfdbcc43"},
{file = "alembic-1.13.1.tar.gz", hash = "sha256:4932c8558bf68f2ee92b9bbcb8218671c627064d5b08939437af6d77dc05e595"},
{file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"},
{file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"},
]
[package.dependencies]
@ -1172,19 +1172,19 @@ files = [
[[package]]
name = "langchain"
version = "0.2.5"
version = "0.2.6"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain-0.2.5-py3-none-any.whl", hash = "sha256:9aded9a65348254e1c93dcdaacffe4d1b6a5e7f74ef80c160c88ff78ad299228"},
{file = "langchain-0.2.5.tar.gz", hash = "sha256:ffdbf4fcea46a10d461bcbda2402220fcfd72a0c70e9f4161ae0510067b9b3bd"},
{file = "langchain-0.2.6-py3-none-any.whl", hash = "sha256:f86e8a7afd3e56f8eb5ba47f01dd00144fb9fc2f1db9873bd197347be2857aa4"},
{file = "langchain-0.2.6.tar.gz", hash = "sha256:867f6add370c1e3911b0e87d3dd0e36aec1e8f513bf06131340fe8f151d89dc5"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
langchain-core = ">=0.2.7,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
langchain-text-splitters = ">=0.2.0,<0.3.0"
langsmith = ">=0.1.17,<0.2.0"
numpy = [
@ -1195,24 +1195,24 @@ pydantic = ">=1,<3"
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-community"
version = "0.2.5"
version = "0.2.6"
description = "Community contributed LangChain integrations."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_community-0.2.5-py3-none-any.whl", hash = "sha256:bf37a334952e42c7676d083cf2d2c4cbfbb7de1949c4149fe19913e2b06c485f"},
{file = "langchain_community-0.2.5.tar.gz", hash = "sha256:476787b8c8c213b67e7b0eceb53346e787f00fbae12d8e680985bd4f93b0bf64"},
{file = "langchain_community-0.2.6-py3-none-any.whl", hash = "sha256:758cc800acfe5dd396bf8ba1b57c4792639ead0eab48ed0367f0732ec6ee1f68"},
{file = "langchain_community-0.2.6.tar.gz", hash = "sha256:40ce09a50ed798aa651ddb34c8978200fa8589b9813c7a28ce8af027bbf249f0"},
]
[package.dependencies]
aiohttp = ">=3.8.3,<4.0.0"
dataclasses-json = ">=0.5.7,<0.7"
langchain = ">=0.2.5,<0.3.0"
langchain-core = ">=0.2.7,<0.3.0"
langchain = ">=0.2.6,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
langsmith = ">=0.1.0,<0.2.0"
numpy = [
{version = ">=1,<2", markers = "python_version < \"3.12\""},
@ -1221,17 +1221,17 @@ numpy = [
PyYAML = ">=5.3"
requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-core"
version = "0.2.9"
version = "0.2.10"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_core-0.2.9-py3-none-any.whl", hash = "sha256:426a5a4fea95a5db995ba5ab560b76edd4998fb6fe52ccc28ac987092a4cbfcd"},
{file = "langchain_core-0.2.9.tar.gz", hash = "sha256:f1c59082642921727844e1cd0eb36d451edd1872c20e193aa3142aac03495986"},
{file = "langchain_core-0.2.10-py3-none-any.whl", hash = "sha256:6eb72086b6bc86db9812da98f79e507c2209a15c0112aefd214a04182ada8586"},
{file = "langchain_core-0.2.10.tar.gz", hash = "sha256:33d1fc234ab58c80476eb5bbde2107ef522a2ce8f46bdf47d9e1bd21e054208f"},
]
[package.dependencies]
@ -1247,35 +1247,32 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-experimental"
version = "0.0.61"
version = "0.0.62"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_experimental-0.0.61-py3-none-any.whl", hash = "sha256:f9c516f528f55919743bd56fe1689a53bf74ae7f8902d64b9d8aebc61249cbe2"},
{file = "langchain_experimental-0.0.61.tar.gz", hash = "sha256:e9538efb994be5db3045cc582cddb9787c8299c86ffeee9d3779b7f58eef2226"},
{file = "langchain_experimental-0.0.62-py3-none-any.whl", hash = "sha256:9240f9e3490e819976f20a37863970036e7baacb7104b9eb6833d19ab6d518c9"},
{file = "langchain_experimental-0.0.62.tar.gz", hash = "sha256:9737fbc8429d24457ea4d368e3c9ba9ed1cace0564fb5f1a96a3027a588bd0ac"},
]
[package.dependencies]
langchain-community = ">=0.2.5,<0.3.0"
langchain-core = ">=0.2.7,<0.3.0"
langchain-community = ">=0.2.6,<0.3.0"
langchain-core = ">=0.2.10,<0.3.0"
[[package]]
name = "langchain-text-splitters"
version = "0.2.1"
version = "0.2.2"
description = "LangChain text splitting utilities"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_text_splitters-0.2.1-py3-none-any.whl", hash = "sha256:c2774a85f17189eaca50339629d2316d13130d4a8d9f1a1a96f3a03670c4a138"},
{file = "langchain_text_splitters-0.2.1.tar.gz", hash = "sha256:06853d17d7241ecf5c97c7b6ef01f600f9b0fb953dd997838142a527a4f32ea4"},
{file = "langchain_text_splitters-0.2.2-py3-none-any.whl", hash = "sha256:1c80d4b11b55e2995f02d2a326c0323ee1eeff24507329bb22924e420c782dff"},
{file = "langchain_text_splitters-0.2.2.tar.gz", hash = "sha256:a1e45de10919fa6fb080ef0525deab56557e9552083600455cb9fa4238076140"},
]
[package.dependencies]
langchain-core = ">=0.2.0,<0.3.0"
[package.extras]
extended-testing = ["beautifulsoup4 (>=4.12.3,<5.0.0)", "lxml (>=4.9.3,<6.0)"]
langchain-core = ">=0.2.10,<0.3.0"
[[package]]
name = "langchainhub"
@ -2482,13 +2479,13 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "sentry-sdk"
version = "2.6.0"
version = "2.7.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = ">=3.6"
files = [
{file = "sentry_sdk-2.6.0-py2.py3-none-any.whl", hash = "sha256:422b91cb49378b97e7e8d0e8d5a1069df23689d45262b86f54988a7db264e874"},
{file = "sentry_sdk-2.6.0.tar.gz", hash = "sha256:65cc07e9c6995c5e316109f138570b32da3bd7ff8d0d0ee4aaf2628c3dd8127d"},
{file = "sentry_sdk-2.7.0-py2.py3-none-any.whl", hash = "sha256:db9594c27a4d21c1ebad09908b1f0dc808ef65c2b89c1c8e7e455143262e37c1"},
{file = "sentry_sdk-2.7.0.tar.gz", hash = "sha256:d846a211d4a0378b289ced3c434480945f110d0ede00450ba631fc2852e7a0d4"},
]
[package.dependencies]
@ -2520,7 +2517,7 @@ langchain = ["langchain (>=0.0.210)"]
loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
opentelemetry-experimental = ["opentelemetry-instrumentation-aio-pika (==0.46b0)", "opentelemetry-instrumentation-aiohttp-client (==0.46b0)", "opentelemetry-instrumentation-aiopg (==0.46b0)", "opentelemetry-instrumentation-asgi (==0.46b0)", "opentelemetry-instrumentation-asyncio (==0.46b0)", "opentelemetry-instrumentation-asyncpg (==0.46b0)", "opentelemetry-instrumentation-aws-lambda (==0.46b0)", "opentelemetry-instrumentation-boto (==0.46b0)", "opentelemetry-instrumentation-boto3sqs (==0.46b0)", "opentelemetry-instrumentation-botocore (==0.46b0)", "opentelemetry-instrumentation-cassandra (==0.46b0)", "opentelemetry-instrumentation-celery (==0.46b0)", "opentelemetry-instrumentation-confluent-kafka (==0.46b0)", "opentelemetry-instrumentation-dbapi (==0.46b0)", "opentelemetry-instrumentation-django (==0.46b0)", "opentelemetry-instrumentation-elasticsearch (==0.46b0)", "opentelemetry-instrumentation-falcon (==0.46b0)", "opentelemetry-instrumentation-fastapi (==0.46b0)", "opentelemetry-instrumentation-flask (==0.46b0)", "opentelemetry-instrumentation-grpc (==0.46b0)", "opentelemetry-instrumentation-httpx (==0.46b0)", "opentelemetry-instrumentation-jinja2 (==0.46b0)", "opentelemetry-instrumentation-kafka-python (==0.46b0)", "opentelemetry-instrumentation-logging (==0.46b0)", "opentelemetry-instrumentation-mysql (==0.46b0)", "opentelemetry-instrumentation-mysqlclient (==0.46b0)", "opentelemetry-instrumentation-pika (==0.46b0)", "opentelemetry-instrumentation-psycopg (==0.46b0)", "opentelemetry-instrumentation-psycopg2 (==0.46b0)", "opentelemetry-instrumentation-pymemcache (==0.46b0)", "opentelemetry-instrumentation-pymongo (==0.46b0)", "opentelemetry-instrumentation-pymysql (==0.46b0)", "opentelemetry-instrumentation-pyramid (==0.46b0)", "opentelemetry-instrumentation-redis (==0.46b0)", "opentelemetry-instrumentation-remoulade (==0.46b0)", "opentelemetry-instrumentation-requests (==0.46b0)", "opentelemetry-instrumentation-sklearn (==0.46b0)", "opentelemetry-instrumentation-sqlalchemy (==0.46b0)", "opentelemetry-instrumentation-sqlite3 (==0.46b0)", "opentelemetry-instrumentation-starlette (==0.46b0)", "opentelemetry-instrumentation-system-metrics (==0.46b0)", "opentelemetry-instrumentation-threading (==0.46b0)", "opentelemetry-instrumentation-tornado (==0.46b0)", "opentelemetry-instrumentation-tortoiseorm (==0.46b0)", "opentelemetry-instrumentation-urllib (==0.46b0)", "opentelemetry-instrumentation-urllib3 (==0.46b0)", "opentelemetry-instrumentation-wsgi (==0.46b0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
pymongo = ["pymongo (>=3.1)"]
pyspark = ["pyspark (>=2.4.4)"]

View file

@ -83,6 +83,7 @@
"build": "vite build",
"serve": "vite preview",
"format": "npx prettier --write \"{tests,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore",
"check-format": "npx prettier --check \"{tests,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore",
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
},
"eslintConfig": {
@ -132,4 +133,4 @@
"ua-parser-js": "^1.0.38",
"vite": "^5.3.1"
}
}
}

View file

@ -15,13 +15,13 @@ dotenv.config({ path: path.resolve(__dirname, "../../.env") });
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: false,
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
retries: process.env.CI ? 2 : 3,
/* Opt out of parallel tests on CI. */
workers: 1,
workers: 2,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
timeout: 120 * 1000,
// reporter: [

View file

@ -21,6 +21,7 @@ export default function HandleRenderComponent({
colors,
setFilterEdge,
showNode,
testIdComplement,
}: {
left: boolean;
nodes: any;
@ -33,6 +34,7 @@ export default function HandleRenderComponent({
colors: string[];
setFilterEdge: any;
showNode: any;
testIdComplement?: string;
}) {
return (
<Button
@ -52,7 +54,7 @@ export default function HandleRenderComponent({
side={left ? "left" : "right"}
>
<Handle
data-test-id={`handle-${title.toLowerCase()}-${
data-testid={`handle-${testIdComplement}-${title.toLowerCase()}-${
!showNode ? (left ? "target" : "source") : left ? "left" : "right"
}`}
type={left ? "target" : "source"}

View file

@ -174,9 +174,10 @@ export default function ParameterComponent({
const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[],
dbValue?: boolean,
skipSnapshot: boolean | undefined = false,
): Promise<void> => {
handleOnNewValueHook(newValue, skipSnapshot);
handleOnNewValueHook(newValue, dbValue, skipSnapshot);
};
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
@ -264,6 +265,7 @@ export default function ParameterComponent({
colors={colors}
setFilterEdge={setFilterEdge}
showNode={showNode}
testIdComplement={`${data?.type?.toLowerCase()}-noshownode`}
/>
)
) : (
@ -390,6 +392,7 @@ export default function ParameterComponent({
colors={colors}
setFilterEdge={setFilterEdge}
showNode={showNode}
testIdComplement={`${data?.type?.toLowerCase()}-shownode`}
/>
)}
@ -468,16 +471,6 @@ export default function ParameterComponent({
<InputGlobalComponent
disabled={disabled}
onChange={handleOnNewValue}
setDb={(value) => {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name].load_from_db = value;
return newNode;
});
}}
name={name}
data={data.node?.template[name]!}
/>

View file

@ -26,7 +26,7 @@ import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useShortcutsStore } from "../../stores/shortcuts";
import { useTypesStore } from "../../stores/typesStore";
import { VertexBuildTypeAPI } from "../../types/api";
import { OutputFieldType, VertexBuildTypeAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
@ -279,7 +279,7 @@ export default function GenericNode({
const shortcuts = useShortcutsStore((state) => state.shortcuts);
const renderOutputParameter = (output, idx) => {
const renderOutputParameter = (output: OutputFieldType, idx: number) => {
return (
<ParameterComponent
index={idx}

View file

@ -1,6 +1,10 @@
import { nodeColors } from "../../utils/styleUtils";
export function getNodeInputColors(input_types, type, types) {
export function getNodeInputColors(
input_types: string[] | undefined,
type: string | undefined,
types: { [char: string]: string },
) {
// Helper function to get the color based on type
const getColorByType = (type) => nodeColors[type] ?? nodeColors.unknown;

View file

@ -1,6 +1,12 @@
import { OutputFieldType } from "../../types/api";
import { NodeDataType } from "../../types/flow";
import { nodeColors } from "../../utils/styleUtils";
export function getNodeOutputColors(output, data, types): string[] {
export function getNodeOutputColors(
output: OutputFieldType,
data: NodeDataType,
types: { [char: string]: string },
): string[] {
// Helper function to get the color based on type
const getColorByType = (type) => nodeColors[type] ?? nodeColors.unknown;

View file

@ -6,13 +6,14 @@ import {
} from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import { ResponseErrorDetailAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
const useFetchDataOnMount = (
data,
name,
handleUpdateValues,
setNode,
setIsLoading,
data: NodeDataType,
name: string,
handleUpdateValues: (name: string, data: NodeDataType) => Promise<any>,
setNode: (id: string, callback: (oldNode: any) => any) => void,
setIsLoading: (value: boolean) => void,
) => {
const setErrorData = useAlertStore((state) => state.setErrorData);

View file

@ -5,19 +5,20 @@ import {
} from "../../constants/constants";
import useAlertStore from "../../stores/alertStore";
import { ResponseErrorTypeAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
const useHandleOnNewValue = (
data,
name,
takeSnapshot,
handleUpdateValues,
debouncedHandleUpdateValues,
setNode,
setIsLoading,
data: NodeDataType,
name: string,
takeSnapshot: () => void,
handleUpdateValues: (name: string, data: NodeDataType) => Promise<any>,
debouncedHandleUpdateValues: any,
setNode: (id: string, callback: (oldNode: any) => any) => void,
setIsLoading: (value: boolean) => void,
) => {
const setErrorData = useAlertStore((state) => state.setErrorData);
const handleOnNewValue = async (newValue, skipSnapshot = false) => {
const handleOnNewValue = async (newValue, dbValue, skipSnapshot = false) => {
const nodeTemplate = data.node!.template[name];
const currentValue = nodeTemplate.value;
@ -62,6 +63,10 @@ const useHandleOnNewValue = (
...newNode.data,
};
if (dbValue) {
newNode.data.node.template[name].load_from_db = dbValue;
}
if (data.node?.template[name].real_time_refresh && newTemplate) {
newNode.data.node.template = newTemplate;
} else {

View file

@ -1,11 +1,12 @@
import { cloneDeep } from "lodash";
import { NodeDataType } from "../../types/flow";
const useHandleNodeClass = (
data,
name,
takeSnapshot,
setNode,
updateNodeInternals,
data: NodeDataType,
name: string,
takeSnapshot: () => void,
setNode: (id: string, callback: (oldNode: any) => any) => void,
updateNodeInternals: (id: string) => void,
) => {
const handleNodeClass = (newNodeClass, code, type?: string) => {
if (!data.node) return;

View file

@ -7,7 +7,10 @@ import useAlertStore from "../../stores/alertStore";
import { ResponseErrorDetailAPI } from "../../types/api";
import { handleUpdateValues } from "../../utils/parameterUtils";
const useHandleRefreshButtonPress = (setIsLoading, setNode) => {
const useHandleRefreshButtonPress = (
setIsLoading: (value: boolean) => void,
setNode: (id: string, callback: (oldNode: any) => any) => void,
) => {
const setErrorData = useAlertStore((state) => state.setErrorData);
const handleRefreshButtonPress = async (name, data) => {

View file

@ -9,7 +9,10 @@ const useIconStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: VertexBuildTypeAPI | null,
) => {
const conditionSuccess = validationStatus && validationStatus.valid;
const conditionSuccess =
!(!buildStatus || buildStatus === BuildStatus.TO_BUILD) &&
validationStatus &&
validationStatus.valid;
const conditionError = buildStatus === BuildStatus.ERROR;
const conditionInactive = buildStatus === BuildStatus.INACTIVE;

View file

@ -1,6 +1,11 @@
import { useEffect } from "react";
import { FlowPoolType } from "../../types/zustand/flow";
const useUpdateValidationStatus = (dataId, flowPool, setValidationStatus) => {
const useUpdateValidationStatus = (
dataId: string,
flowPool: FlowPoolType,
setValidationStatus: (value: any) => void,
) => {
useEffect(() => {
const relevantData =
flowPool[dataId] && flowPool[dataId]?.length > 0

View file

@ -4,7 +4,7 @@ import { isErrorLog } from "../../types/utils/typeCheckingUtils";
const useValidationStatusString = (
validationStatus: VertexBuildTypeAPI | null,
setValidationString,
setValidationString: (value: any) => void,
) => {
useEffect(() => {
if (validationStatus && validationStatus.data?.outputs) {

View file

@ -6,7 +6,7 @@ import useAlertStore from "../../stores/alertStore";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";
export default function ImageViewer({ image }) {
export default function ImageViewer({ image }: { image: string }) {
const viewerRef = useRef(null);
const [errorDownloading, setErrordownloading] = useState(false);
const setErrorList = useAlertStore((state) => state.setErrorData);

View file

@ -29,7 +29,9 @@ export default function AddNewVariableButton({
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set(
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)),
Object.keys(
useGlobalVariablesStore((state) => state.unavaliableFields) ?? {},
),
);
const availableFields = () => {

View file

@ -0,0 +1,19 @@
import { useEffect } from "react";
import { storeComponent } from "../../../types/store";
const useDataEffect = (
data: storeComponent,
setLikedByUser: (value: any) => void,
setLikesCount: (value: any) => void,
setDownloadsCount: (value: any) => void,
) => {
useEffect(() => {
if (data) {
setLikedByUser(data?.liked_by_user ?? false);
setLikesCount(data?.liked_by_count ?? 0);
setDownloadsCount(data?.downloads_count ?? 0);
}
}, [data, data?.liked_by_count, data?.liked_by_user, data?.downloads_count]);
};
export default useDataEffect;

View file

@ -0,0 +1,55 @@
import { useState } from "react";
import { getComponent } from "../../../controllers/API";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { storeComponent } from "../../../types/store";
import cloneFlowWithParent from "../../../utils/storeUtils";
const useInstallComponent = (
data: storeComponent,
name: string,
isStore: boolean,
downloadsCount: number,
setDownloadsCount: (value: any) => void,
setLoading: (value: boolean) => void,
setSuccessData: (value: { title: string }) => void,
setErrorData: (value: { title: string; list: string[] }) => void,
) => {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const handleInstall = () => {
const temp = downloadsCount;
setDownloadsCount((old) => Number(old) + 1);
setLoading(true);
getComponent(data.id)
.then((res) => {
const newFlow = cloneFlowWithParent(res, res.id, data.is_component);
addFlow(true, newFlow)
.then((id) => {
setSuccessData({
title: `${name} ${isStore ? "Downloaded" : "Installed"} Successfully.`,
});
setLoading(false);
})
.catch((error) => {
setLoading(false);
setErrorData({
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
list: [error.response.data.detail],
});
});
})
.catch((err) => {
setLoading(false);
setErrorData({
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
list: [err.response.data.detail],
});
setDownloadsCount(temp);
});
};
return { handleInstall };
};
export default useInstallComponent;

View file

@ -0,0 +1,51 @@
import { postLikeComponent } from "../../../controllers/API";
import { storeComponent } from "../../../types/store";
const useLikeComponent = (
data: storeComponent,
name: string,
setLoadingLike: (value: boolean) => void,
likedByUser: boolean | null | undefined,
likesCount: number,
setLikedByUser: (value: any) => void,
setLikesCount: (value: any) => void,
setValidApiKey: (value: boolean) => void,
setErrorData: (value: { title: string; list: string[] }) => void,
) => {
const handleLike = () => {
setLoadingLike(true);
if (likedByUser !== undefined || likedByUser !== null) {
const temp = likedByUser;
const tempNum = likesCount;
setLikedByUser((prev) => !prev);
setLikesCount((prev) => (temp ? prev - 1 : prev + 1));
postLikeComponent(data.id)
.then((response) => {
setLoadingLike(false);
setLikesCount(response.data.likes_count);
setLikedByUser(response.data.liked_by_user);
})
.catch((error) => {
setLoadingLike(false);
setLikesCount(tempNum);
setLikedByUser(temp);
if (error.response.status === 403) {
setValidApiKey(false);
} else {
console.error(error);
setErrorData({
title: `Error liking ${name}.`,
list: [error.response.data.detail],
});
}
});
}
};
return {
handleLike,
};
};
export default useLikeComponent;

View file

@ -0,0 +1,34 @@
import { useCallback } from "react";
import { createRoot } from "react-dom/client";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { storeComponent } from "../../../types/store";
import DragCardComponent from "../components/dragCardComponent";
const useDragStart = (data: storeComponent) => {
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
const onDragStart = useCallback(
(event) => {
let image = <DragCardComponent data={data} />; // Replace with whatever you want here
const ghost = document.createElement("div");
ghost.style.transform = "translate(-10000px, -10000px)";
ghost.style.position = "absolute";
document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 0, 0);
const root = createRoot(ghost);
root.render(image);
const flow = getFlowById(data.id);
if (flow) {
event.dataTransfer.setData("flow", JSON.stringify(data));
}
},
[data],
);
return { onDragStart };
};
export default useDragStart;

View file

@ -0,0 +1,27 @@
import { useEffect } from "react";
import { FlowType } from "../../../types/flow";
const usePlaygroundEffect = (
currentFlowId: string,
playground: boolean,
openPlayground: boolean,
currentFlow: FlowType | undefined,
setNodes: (value: any, value2: boolean) => void,
setEdges: (value: any, value2: boolean) => void,
cleanFlowPool: () => void,
) => {
useEffect(() => {
if (currentFlowId && playground) {
if (openPlayground) {
setNodes(currentFlow?.data?.nodes ?? [], true);
setEdges(currentFlow?.data?.edges ?? [], true);
} else {
setNodes([], true);
setEdges([], true);
}
cleanFlowPool();
}
}, [openPlayground]);
};
export default usePlaygroundEffect;

View file

@ -28,6 +28,11 @@ import { Checkbox } from "../ui/checkbox";
import { FormControl, FormField } from "../ui/form";
import Loading from "../ui/loading";
import DragCardComponent from "./components/dragCardComponent";
import useDataEffect from "./hooks/use-data-effect";
import useInstallComponent from "./hooks/use-handle-install";
import useLikeComponent from "./hooks/use-handle-like";
import useDragStart from "./hooks/use-on-drag-start";
import usePlaygroundEffect from "./hooks/use-playground-effect";
import { convertTestName } from "./utils/convert-test-name";
export default function CollectionCardComponent({
@ -59,11 +64,9 @@ export default function CollectionCardComponent({
const isStore = false;
const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false);
const [liked_by_user, setLiked_by_user] = useState(
data?.liked_by_user ?? false,
);
const [likes_count, setLikes_count] = useState(data?.liked_by_count ?? 0);
const [downloads_count, setDownloads_count] = useState(
const [likedByUser, setLikedByUser] = useState(data?.liked_by_user ?? false);
const [likesCount, setLikesCount] = useState(data?.liked_by_count ?? 0);
const [downloadsCount, setDownloadsCount] = useState(
data?.downloads_count ?? 0,
);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
@ -99,115 +102,45 @@ export default function CollectionCardComponent({
return inputs.length > 0 || outputs.length > 0;
}
useEffect(() => {
if (currentFlowId && playground) {
if (openPlayground) {
setNodes(currentFlow?.data?.nodes ?? [], true);
setEdges(currentFlow?.data?.edges ?? [], true);
} else {
setNodes([], true);
setEdges([], true);
}
cleanFlowPool();
}
}, [openPlayground]);
usePlaygroundEffect(
currentFlowId,
playground!,
openPlayground,
currentFlow,
setNodes,
setEdges,
cleanFlowPool,
);
useEffect(() => {
if (data) {
setLiked_by_user(data?.liked_by_user ?? false);
setLikes_count(data?.liked_by_count ?? 0);
setDownloads_count(data?.downloads_count ?? 0);
}
}, [data, data.liked_by_count, data.liked_by_user, data.downloads_count]);
useDataEffect(data, setLikedByUser, setLikesCount, setDownloadsCount);
function handleInstall() {
const temp = downloads_count;
setDownloads_count((old) => Number(old) + 1);
setLoading(true);
getComponent(data.id)
.then((res) => {
const newFlow = cloneFLowWithParent(res, res.id, data.is_component);
addFlow(true, newFlow)
.then((id) => {
setSuccessData({
title: `${name} ${
isStore ? "Downloaded" : "Installed"
} Successfully.`,
});
setLoading(false);
})
.catch((error) => {
setLoading(false);
setErrorData({
title: `Error ${
isStore ? "downloading" : "installing"
} the ${name}`,
list: [error["response"]["data"]["detail"]],
});
});
})
.catch((err) => {
setLoading(false);
setErrorData({
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
list: [err["response"]["data"]["detail"]],
});
setDownloads_count(temp);
});
}
const { handleInstall } = useInstallComponent(
data,
name,
isStore,
downloadsCount,
setDownloadsCount,
setLoading,
setSuccessData,
setErrorData,
);
function handleLike() {
setLoadingLike(true);
if (liked_by_user !== undefined || liked_by_user !== null) {
const temp = liked_by_user;
const tempNum = likes_count;
setLiked_by_user((prev) => !prev);
if (!temp) {
setLikes_count((prev) => Number(prev) + 1);
} else {
setLikes_count((prev) => Number(prev) - 1);
}
postLikeComponent(data.id)
.then((response) => {
setLoadingLike(false);
setLikes_count(response.data.likes_count);
setLiked_by_user(response.data.liked_by_user);
})
.catch((error) => {
setLoadingLike(false);
setLikes_count(tempNum);
setLiked_by_user(temp);
if (error.response.status === 403) {
setValidApiKey(false);
} else {
console.error(error);
setErrorData({
title: `Error liking ${name}.`,
list: [error["response"]["data"]["detail"]],
});
}
});
}
}
const { handleLike } = useLikeComponent(
data,
name,
setLoadingLike,
likedByUser,
likesCount,
setLikedByUser,
setLikesCount,
setValidApiKey,
setErrorData,
);
const isSelectedCard =
selectedFlowsComponentsCards?.includes(data?.id) ?? false;
function onDragStart(event: React.DragEvent<any>) {
let image: JSX.Element = <DragCardComponent data={data} />; // <== whatever you want here
var ghost = document.createElement("div");
ghost.style.transform = "translate(-10000px, -10000px)";
ghost.style.position = "absolute";
document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 0, 0);
const root = createRoot(ghost);
root.render(image);
const flow = getFlowById(data.id);
if (flow) {
event.dataTransfer.setData("flow", JSON.stringify(data));
}
}
const { onDragStart } = useDragStart(data);
return (
<>
@ -264,7 +197,7 @@ export default function CollectionCardComponent({
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="Heart" className={cn("h-4 w-4")} />
<span data-testid={`likes-${data.name}`}>
{likes_count ?? 0}
{likesCount ?? 0}
</span>
</span>
</ShadTooltip>
@ -275,7 +208,7 @@ export default function CollectionCardComponent({
className="h-4 w-4"
/>
<span data-testid={`downloads-${data.name}`}>
{downloads_count ?? 0}
{downloadsCount ?? 0}
</span>
</span>
</ShadTooltip>
@ -296,6 +229,7 @@ export default function CollectionCardComponent({
render={({ field }) => (
<FormControl>
<Checkbox
data-testid={`checkbox-component`}
aria-label="checkbox-component"
checked={field.value}
onCheckedChange={field.onChange}
@ -324,24 +258,19 @@ export default function CollectionCardComponent({
)}
</span>
)}
<div className="flex w-full flex-1 flex-wrap gap-2">
{/* {data.tags &&
data.tags.length > 0 &&
data.tags.map((tag, index) => (
<Badge
key={index}
variant="outline"
size="xq"
className="text-muted-foreground"
>
{tag.name}
</Badge>
))} */}
</div>
<div className="flex w-full flex-1 flex-wrap gap-2"></div>
</div>
<CardDescription className="pb-2 pt-2">
<div className="truncate-doubleline">{data.description}</div>
<div
className={
data?.metadata !== undefined
? "truncate"
: "truncate-doubleline"
}
>
{data.description}
</div>
</CardDescription>
</CardHeader>
</div>
@ -457,7 +386,7 @@ export default function CollectionCardComponent({
name="Heart"
className={cn(
"h-5 w-5",
liked_by_user
likedByUser
? "fill-destructive stroke-destructive"
: "",
!authorized ? "text-ring" : "",

View file

@ -67,6 +67,7 @@ export default function FlowToolbar(): JSX.Element {
? "button-disable text-muted-foreground"
: "",
)}
data-testid="shared-button-flow"
>
<ForwardedIconComponent
name="Share3"

View file

@ -23,7 +23,7 @@ export default function CodeAreaComponent({
useEffect(() => {
if (disabled && myValue !== "") {
setMyValue("");
onChange("", true);
onChange("", undefined, true);
}
}, [disabled]);

View file

@ -26,6 +26,7 @@ import {
TabsTrigger,
} from "../../components/ui/tabs";
import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants";
import getTabsOrder from "../../modals/apiModal/utils/get-tabs-order";
import { Case } from "../../shared/components/caseComponent";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
@ -56,6 +57,8 @@ export default function CodeTabsComponent({
setActiveTweaks,
activeTweaks,
allowExport = false,
isThereTweaks = false,
isThereWH = false,
}: codeTabsPropsType) {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
@ -93,6 +96,8 @@ export default function CodeTabsComponent({
return node.data.node.template[templateParam].type;
};
const tabsOrder = getTabsOrder(isThereWH, isThereTweaks);
return (
<Tabs
value={activeTab}
@ -172,7 +177,7 @@ export default function CodeTabsComponent({
className="api-modal-tabs-content overflow-hidden"
key={idx} // Remember to add a unique key prop
>
{idx < 5 ? (
{tabsOrder[idx].toLowerCase() !== "tweaks" ? (
<div className="flex h-full w-full flex-col">
{tab.description && (
<div
@ -188,7 +193,7 @@ export default function CodeTabsComponent({
{tab.code}
</SyntaxHighlighter>
</div>
) : idx === 5 ? (
) : tabsOrder[idx].toLowerCase() === "tweaks" ? (
<>
<div className="api-modal-according-display">
<div

View file

@ -10,9 +10,14 @@ function DataOutputComponent({
columnMode = "union",
}: {
pagination: boolean;
rows: any;
rows: any[];
columnMode?: "intersection" | "union";
}) {
// If the rows are not an array of objects, convert them to an array of objects
if (rows.some((row) => typeof row !== "object")) {
rows = rows.map((row) => ({ data: row }));
}
const columns = extractColumnsFromRows(rows, columnMode);
const columnDefs = columns.map((col, idx) => ({

View file

@ -4,7 +4,7 @@ import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { InputProps } from "../../types/components";
import { cn } from "../../utils/utils";
import { cn, isEndpointNameValid } from "../../utils/utils";
export const EditFlowSettings: React.FC<InputProps> = ({
name,
@ -17,7 +17,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
setEndpointName,
}: InputProps): JSX.Element => {
const [isMaxLength, setIsMaxLength] = useState(false);
const [isEndpointNameValid, setIsEndpointNameValid] = useState(true);
const [validEndpointName, setValidEndpointName] = useState(true);
const [isInvalidName, setIsInvalidName] = useState(false);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
@ -34,10 +34,6 @@ export const EditFlowSettings: React.FC<InputProps> = ({
invalid = true;
break;
}
if (value === currentFlow?.name) {
invalid = true;
break;
}
invalid = false;
}
setIsInvalidName(invalid);
@ -51,12 +47,8 @@ export const EditFlowSettings: React.FC<InputProps> = ({
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
// Validate the endpoint name
// use this regex r'^[a-zA-Z0-9_-]+$'
const isValid =
(/^[a-zA-Z0-9_-]+$/.test(event.target.value) &&
event.target.value.length <= maxLength) ||
// empty is also valid
event.target.value.length === 0;
setIsEndpointNameValid(isValid);
const isValid = isEndpointNameValid(event.target.value, maxLength);
setValidEndpointName(isValid);
setEndpointName!(event.target.value);
};
@ -115,21 +107,21 @@ export const EditFlowSettings: React.FC<InputProps> = ({
}}
/>
) : (
<span
<div
className={cn(
"font-normal text-muted-foreground word-break-break-word",
"max-h-[250px] overflow-auto font-normal text-muted-foreground word-break-break-word",
description === "" ? "font-light italic" : "",
)}
>
{description === "" ? "No description" : description}
</span>
</div>
)}
</Label>
{setEndpointName && (
<Label>
<div className="edit-flow-arrangement mt-3">
<span className="font-medium">Endpoint Name</span>
{!isEndpointNameValid && (
{!validEndpointName && (
<span className="edit-flow-span">
Invalid endpoint name. Use only letters, numbers, hyphens, and
underscores ({maxLength} characters max).

Some files were not shown because too many files have changed in this diff Show more