mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 17:07:08 +08:00
* feat: upgrade Docker images to Python 3.14 (experimental) ## Changes - Update pyproject.toml: requires-python from <3.14 to <3.15 - Update all 5 Dockerfiles to use Python 3.14: - Builder stage: python3.12-trixie-slim → python3.14-trixie-slim - Runtime stage: python:3.12-slim-trixie → python:3.14-slim-trixie ## Files Updated - src/backend/base/pyproject.toml - docker/build_and_push_base.Dockerfile - docker/build_and_push.Dockerfile - docker/build_and_push_backend.Dockerfile - docker/build_and_push_with_extras.Dockerfile - docker/build_and_push_ep.Dockerfile ## Rationale Python 3.14.5 was released on May 10, 2026. Upgrading to the latest Python version should help reduce CVE vulnerabilities in Docker images. ## Testing Strategy This is an experimental change to test Python 3.14 compatibility: - Docker images will use Python 3.14 - CI/CD workflows still test on Python 3.10-3.13 - Nightly build will validate if dependencies work with 3.14 - Can be reverted quickly if issues are found ## Next Steps - Monitor nightly build for failures - If successful, update CI/CD workflows to add Python 3.14 to test matrix - If failures occur, revert and investigate compatibility issues * chore: support Python 3.14 Bump requires-python upper bound to <3.15 across langflow, langflow-base, lfx, and langflow-sdk, and add 3.14 to CI test matrices so PRs are gated on 3.14 compatibility. Conditional pins for transitive deps without 3.14 wheels at the existing caps: - onnxruntime: >=1.26 on 3.14 (existing <1.24 cap retained for 3.10) - faiss-cpu: >=1.13.2 on 3.14 (existing ==1.9.0.post1 retained for <3.14) * chore: marker-gate IBM watsonx packages for Python 3.14 ibm-watsonx-ai 1.3.x cannot import on Python 3.14: its StrEnum subclasses override __init__ in a way that conflicts with 3.14's reworked enum __set_name__ path (TypeError on KnowledgeBaseFieldRole class creation). Cap ibm-watsonx-ai, langchain-ibm, and the ibm-watsonx-orchestrate-* extras at python_version<'3.14' until upstream adapts. Watsonx component imports are already lazy, so this surfaces only at component-access time on 3.14, where the existing ImportError handler in lfx.components.ibm.__init__ degrades it gracefully. test_model_utils.py imports ChatWatsonx at module top to verify get_model_name resolves model_id; guard that import so the test module skips on 3.14 instead of breaking collection. * [autofix.ci] apply automated fixes * chore: upgrade remaining Docker images to Python 3.14 build_and_push*.Dockerfile already moved to 3.14 in the merge from feat/upgrade-python-3.14-docker-images. Apply the same bump to the dev, devcontainer, and lfx Docker images so all in-repo Dockerfiles share one Python version. * [autofix.ci] apply automated fixes * chore: gate 3.14-broken extras to python_version<'3.14' cuga (and its transitive fastembed -> py-rust-stemmers source build), altk/agent-lifecycle-toolkit (re-pulls ibm-watsonx-ai which was already gated), langchain-pinecone, langwatch, ragstack-ai-knowledge-store, and OpenDsStar all pin themselves below 3.14 upstream. Without a marker guard, the workspace lock still tries to install them and a Docker `uv sync` then attempts source builds that require Rust (CI Docker test failure with py-rust-stemmers 0.1.5). Add python_version<'3.14' (or <'3.13' for ragstack) to each pin so those packages are simply omitted from the 3.14 install set until upstream catches up. uv pip check is now clean on 3.14.2. * test: skip test_altk_agent on Python 3.14 altk (agent-lifecycle-toolkit) is gated to python_version<'3.14' upstream and now via the langflow-base [altk] extra marker. The test imports altk at module top to exercise ALTKAgentComponent; guard the import so collection skips on 3.14 instead of failing. * test: skip remaining altk tests on Python 3.14 Three more test modules import lfx.base.agents.altk_* at module top, which transitively imports altk. Add the same module-level skip guard as test_altk_agent.py: - test_altk_agent_logic.py - test_altk_agent_tool_conversion.py - test_conversation_context_ordering.py * fix(calculator): replace removed ast.Num with ast.Constant for Python 3.14 ast.Num was deprecated in Python 3.8 in favor of ast.Constant and removed entirely in 3.14. The calculator tool and its core walker crashed on 3.14 with 'module ast has no attribute Num'. Switch the isinstance checks to ast.Constant + isinstance(node.value, (int, float)), and drop the now-dead ast.Num backwards-compat branch in calculator_core.py (the ast.Constant branch already handles every input it would have matched). Update the parser unit-test fixtures to construct ast.Constant nodes. * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * test: skip LangWatch HTTP instrumentation tests on Python 3.14 langwatch is gated to python_version<'3.14' upstream. The TestLangWatchHttpInstrumentation class patches langwatch.setup and langwatch.trace inside a fixture, which requires langwatch to be importable; on 3.14 the test errors with ModuleNotFoundError. Guard the class with a skipif on langwatch availability. * test: allow IBM and altk components to be missing on Python 3.14 test_all_modules_importable enforces that every component in __all__ imports cleanly. On 3.14 the ibm-watsonx-ai, langchain-ibm, and altk packages are gated upstream and intentionally not installed, so the WatsonxAI, WatsonxEmbeddings, and ALTKAgent components fail to import as designed. - test_all_components_in_categories_importable: accept a known-gated deny-list on 3.14 instead of failing. - test_all_lfx_component_modules_directly_importable: extend the existing 'missing optional dependency' allowlist with altk, langchain_ibm, and ibm_watsonx_ai. * fix(lfx): pass ensure_exists to user_cache_dir for Python 3.14 Python 3.14 tightened PurePath.__init__ to reject unknown keyword arguments. The KeyedWorkerLockManager constructor was passing ensure_exists=True to Path() instead of user_cache_dir(), which silently worked on 3.10-3.13 but raises TypeError on 3.14 and also meant the cache directory was never actually being created. Move the kwarg to user_cache_dir() where it belongs and update the two unit tests that asserted the buggy call shape. * [autofix.ci] apply automated fixes * test: accept either ZIP or JSON error path on Python 3.14 Python 3.14's zipfile.is_zipfile() now validates the central-directory signature in addition to EOCD, so the garbage+EOCD payload no longer passes the is_zipfile() dispatch in the /flows/upload/ route. The endpoint still returns 400 with a descriptive detail (now from the JSON branch); only the message wording differs, which the test was asserting verbatim. * fix(lfx): normalize cors_origins ['*'] back to '*' on Python 3.14 Pydantic-settings on Python 3.14 parses the env var '*' into ['*'] before the cors_origins field validator runs (the list[str] | str union resolves differently than on 3.10-3.13). Collapse that back to the bare-string wildcard so downstream consumers — including warn_about_future_cors_changes and the test suite — see the same shape on every supported Python version. --------- Co-authored-by: vijay kumar katuri <vijay.katuri@ibm.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
403 lines
13 KiB
YAML
403 lines
13 KiB
YAML
name: LFX Release
|
|
run-name: LFX Release ${{ github.event.inputs.version || 'dev' }} by @${{ github.actor }}
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version to release (e.g., 0.1.0)"
|
|
required: true
|
|
type: string
|
|
publish_pypi:
|
|
description: "Publish to PyPI"
|
|
required: true
|
|
type: boolean
|
|
default: true
|
|
build_docker:
|
|
description: "Build and publish Docker images"
|
|
required: true
|
|
type: boolean
|
|
default: true
|
|
pre_release:
|
|
description: "Mark as pre-release"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
create_github_release:
|
|
description: "Create GitHub release"
|
|
required: true
|
|
type: boolean
|
|
default: true
|
|
|
|
env:
|
|
PYTHON_VERSION: "3.13"
|
|
|
|
permissions:
|
|
contents: write
|
|
packages: write
|
|
|
|
jobs:
|
|
validate-version:
|
|
name: Validate Version
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should_release: ${{ steps.check.outputs.should_release }}
|
|
current_version: ${{ steps.check.outputs.current_version }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Environment
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
prune-cache: false
|
|
|
|
- name: Check version
|
|
id: check
|
|
run: |
|
|
cd src/lfx
|
|
# Use uv tree to get package info, consistent with nightly workflow
|
|
name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}')
|
|
version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}')
|
|
|
|
# Strip leading 'v' if present
|
|
version=$(echo $version | sed 's/^v//')
|
|
echo "current_version=$version" >> $GITHUB_OUTPUT
|
|
|
|
if [ "$version" != "${{ github.event.inputs.version }}" ]; then
|
|
echo "❌ Version mismatch: package has $version but input is ${{ github.event.inputs.version }}"
|
|
echo "Please update the version in pyproject.toml first"
|
|
echo "should_release=false" >> $GITHUB_OUTPUT
|
|
exit 1
|
|
fi
|
|
|
|
# Check if version already exists on PyPI
|
|
if curl -s "https://pypi.org/pypi/lfx/json" | jq -r '.releases | keys[]' | grep -q "^${{ github.event.inputs.version }}$"; then
|
|
echo "❌ Version ${{ github.event.inputs.version }} already exists on PyPI"
|
|
echo "should_release=false" >> $GITHUB_OUTPUT
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Version ${{ github.event.inputs.version }} is valid and not yet released"
|
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
|
|
|
run-tests:
|
|
name: Run Tests
|
|
needs: validate-version
|
|
if: needs.validate-version.outputs.should_release == 'true'
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Environment
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ matrix.python-version }}
|
|
prune-cache: false
|
|
|
|
- name: Run LFX tests
|
|
run: |
|
|
cd src/lfx
|
|
make test
|
|
|
|
- name: Test CLI installation
|
|
run: |
|
|
cd src/lfx
|
|
uv pip install .
|
|
uv run lfx --help
|
|
uv run lfx run --help
|
|
uv run lfx serve --help
|
|
|
|
release-lfx:
|
|
name: Build and Release LFX
|
|
needs: [validate-version, run-tests]
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.check-version.outputs.version }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Environment
|
|
uses: astral-sh/setup-uv@v6
|
|
with:
|
|
enable-cache: true
|
|
cache-dependency-glob: "uv.lock"
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
prune-cache: false
|
|
|
|
- name: Install LFX dependencies
|
|
run: uv sync --dev --package lfx
|
|
|
|
- name: Verify Version
|
|
id: check-version
|
|
run: |
|
|
cd src/lfx
|
|
# Use uv tree to get package info, consistent with nightly workflow
|
|
name=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $1}')
|
|
version=$(uv tree | grep 'lfx' | head -n 1 | awk '{print $2}')
|
|
|
|
# Verify package name
|
|
if [ "$name" != "lfx" ]; then
|
|
echo "Package name $name does not match lfx. Exiting the workflow."
|
|
exit 1
|
|
fi
|
|
|
|
# Strip leading 'v' if present
|
|
version=$(echo $version | sed 's/^v//')
|
|
|
|
# Verify version matches input
|
|
if [ "$version" != "${{ github.event.inputs.version }}" ]; then
|
|
echo "Version $version does not match input ${{ github.event.inputs.version }}. Exiting the workflow."
|
|
exit 1
|
|
fi
|
|
|
|
echo "version=$version" >> $GITHUB_OUTPUT
|
|
|
|
- name: Build distribution
|
|
run: |
|
|
cd src/lfx
|
|
rm -rf dist/
|
|
uv build --wheel --out-dir dist
|
|
|
|
- name: Check build artifacts
|
|
run: |
|
|
cd src/lfx
|
|
ls -la dist/
|
|
# Verify wheel contents
|
|
unzip -l dist/*.whl | grep -E "(lfx/__main__.py|lfx/cli/run.py|lfx/cli/commands.py)"
|
|
|
|
- name: Test installation from wheel
|
|
run: |
|
|
cd src/lfx
|
|
uv pip install dist/*.whl --force-reinstall
|
|
uv run lfx --help
|
|
echo "LFX CLI test completed successfully"
|
|
|
|
- name: Upload artifacts
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: lfx-dist
|
|
path: src/lfx/dist/
|
|
retention-days: 5
|
|
|
|
- name: Publish to PyPI
|
|
if: github.event.inputs.publish_pypi == 'true'
|
|
env:
|
|
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
run: |
|
|
cd src/lfx
|
|
uv publish dist/*.whl
|
|
|
|
build-docker:
|
|
name: Build Docker Images
|
|
needs: [validate-version, run-tests]
|
|
if: github.event.inputs.build_docker == 'true'
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
matrix:
|
|
variant: [production, alpine]
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@v3
|
|
|
|
- name: Docker System Info and Cleanup
|
|
run: |
|
|
echo "=== Docker System Usage Before Cleanup ==="
|
|
docker system df || true
|
|
docker buildx du || true
|
|
|
|
echo "=== Cleaning up Docker System ==="
|
|
docker system prune -af --volumes || true
|
|
docker buildx prune -af || true
|
|
|
|
echo "=== Docker System Usage After Cleanup ==="
|
|
docker system df || true
|
|
docker buildx du || true
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Login to Docker Hub
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
|
|
- name: Login to GitHub Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Prepare Docker metadata
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: |
|
|
langflowai/lfx
|
|
ghcr.io/langflow-ai/lfx
|
|
tags: |
|
|
type=raw,value=${{ github.event.inputs.version }}${{ matrix.variant == 'alpine' && '-alpine' || '' }}
|
|
type=raw,value=latest${{ matrix.variant == 'alpine' && '-alpine' || '' }},enable=${{ github.event.inputs.pre_release == 'false' }}
|
|
labels: |
|
|
org.opencontainers.image.title=LFX
|
|
org.opencontainers.image.description=Langflow Executor - CLI tool for running Langflow AI workflows
|
|
org.opencontainers.image.vendor=Langflow
|
|
org.opencontainers.image.version=${{ github.event.inputs.version }}
|
|
|
|
- name: Build and push Docker image
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
file: src/lfx/docker/Dockerfile${{ matrix.variant == 'alpine' && '.alpine' || '' }}
|
|
platforms: linux/amd64,linux/arm64
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
build-args: |
|
|
LFX_VERSION=${{ github.event.inputs.version }}
|
|
|
|
create-release:
|
|
name: Create GitHub Release
|
|
needs: [release-lfx, build-docker]
|
|
if: always() && github.event.inputs.create_github_release == 'true' && needs.release-lfx.result == 'success'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Download artifacts
|
|
uses: actions/download-artifact@v7
|
|
with:
|
|
name: lfx-dist
|
|
path: dist/
|
|
|
|
- name: Generate release notes
|
|
id: notes
|
|
run: |
|
|
cat > release_notes.md << EOF
|
|
# LFX ${{ github.event.inputs.version }}
|
|
|
|
## 🚀 Installation
|
|
|
|
### PyPI
|
|
\`\`\`bash
|
|
pip install lfx==${{ github.event.inputs.version }}
|
|
# or
|
|
uv pip install lfx==${{ github.event.inputs.version }}
|
|
# or run without installing
|
|
uvx lfx@${{ github.event.inputs.version }} --help
|
|
\`\`\`
|
|
|
|
### Docker
|
|
\`\`\`bash
|
|
# Standard image
|
|
docker pull langflowai/lfx:${{ github.event.inputs.version }}
|
|
|
|
# Alpine image (smaller)
|
|
docker pull langflowai/lfx:${{ github.event.inputs.version }}-alpine
|
|
|
|
# Run a flow
|
|
docker run --rm -v \$(pwd):/app/data langflowai/lfx:${{ github.event.inputs.version }} lfx run flow.json --input-value "Hello"
|
|
\`\`\`
|
|
|
|
## 📦 What's New
|
|
|
|
<!-- Add release notes here -->
|
|
|
|
## 📋 Checksums
|
|
|
|
\`\`\`
|
|
$(cd dist && sha256sum *)
|
|
\`\`\`
|
|
|
|
---
|
|
|
|
**Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ needs.validate-version.outputs.current_version }}...lfx-v${{ github.event.inputs.version }}
|
|
EOF
|
|
|
|
- name: Create Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: lfx-v${{ github.event.inputs.version }}
|
|
name: LFX ${{ github.event.inputs.version }}
|
|
body_path: release_notes.md
|
|
draft: false
|
|
prerelease: ${{ github.event.inputs.pre_release }}
|
|
files: |
|
|
dist/*
|
|
generate_release_notes: true
|
|
|
|
test-release:
|
|
name: Test Release
|
|
needs: [release-lfx, build-docker]
|
|
if: always() && (needs.release-lfx.result == 'success' || needs.build-docker.result == 'success')
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Wait for PyPI propagation
|
|
if: needs.release-lfx.result == 'success'
|
|
run: sleep 60
|
|
|
|
- name: Test PyPI installation
|
|
if: needs.release-lfx.result == 'success'
|
|
run: |
|
|
# Test installation using uv
|
|
uv pip install lfx==${{ github.event.inputs.version }}
|
|
uv run lfx --help
|
|
|
|
- name: Test Docker image
|
|
if: needs.build-docker.result == 'success'
|
|
run: |
|
|
# Test standard image
|
|
docker run --rm langflowai/lfx:${{ github.event.inputs.version }} lfx --help
|
|
|
|
# Test alpine image
|
|
docker run --rm langflowai/lfx:${{ github.event.inputs.version }}-alpine lfx --help
|
|
|
|
# Test with a simple flow
|
|
cat > test_flow.json << 'EOF'
|
|
{
|
|
"nodes": [],
|
|
"edges": []
|
|
}
|
|
EOF
|
|
|
|
docker run --rm -v $(pwd):/app/data langflowai/lfx:${{ github.event.inputs.version }} \
|
|
lfx run /app/data/test_flow.json --input-value "test" || true
|
|
|
|
notify:
|
|
name: Notify Release Status
|
|
needs: [create-release, test-release]
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Notify success
|
|
if: needs.create-release.result == 'success'
|
|
run: |
|
|
echo "✅ LFX ${{ github.event.inputs.version }} released successfully!"
|
|
echo "PyPI: https://pypi.org/project/lfx/${{ github.event.inputs.version }}/"
|
|
echo "Docker Hub: https://hub.docker.com/r/langflowai/lfx/tags"
|
|
echo "GitHub Release: https://github.com/${{ github.repository }}/releases/tag/lfx-v${{ github.event.inputs.version }}"
|
|
|
|
- name: Notify failure
|
|
if: needs.create-release.result != 'success'
|
|
run: |
|
|
echo "❌ LFX ${{ github.event.inputs.version }} release failed!"
|
|
exit 1 |