Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies = [
"lfx-arxiv>=0.1.0",
"lfx-ibm>=0.1.0",
"lfx-docling>=0.1.0",
"lfx-bundles[all]>=1.0,<2.0",
# langflow-extensions:bundle-deps-end
]

Expand Down Expand Up @@ -84,6 +85,7 @@ lfx-duckduckgo = { workspace = true }
lfx-arxiv = { workspace = true }
lfx-ibm = { workspace = true }
lfx-docling = { workspace = true }
lfx-bundles = { workspace = true }
# langflow-extensions:bundle-sources-end
torch = { index = "pytorch-cpu" }
torchvision = { index = "pytorch-cpu" }
Expand All @@ -100,6 +102,7 @@ members = [
"src/bundles/arxiv",
"src/bundles/ibm",
"src/bundles/docling",
"src/bundles/lfx-bundles",
# langflow-extensions:bundle-members-end
]

Expand Down
7 changes: 4 additions & 3 deletions scripts/ci/sync_bundle_lfx_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
unconditionally from ``make patch``, including patch releases within a minor
line where the floor does not move). Only the bundle's ``"lfx<op>..."``
runtime dependency is rewritten -- self-references such as
``"lfx-docling[local]"`` and the nightly ``"lfx-nightly=="`` form are left
``"lfx-docling[local]"`` and the legacy ``"lfx-nightly=="`` form are left
untouched (neither has a bare version operator immediately after ``lfx``).

Stdlib only, so it runs in any CI checkout (same constraint as the sibling
``scripts/ci/update_bundle_versions.py`` and ``scripts/migrate/port_bundle.py``).
``scripts/migrate/port_bundle.py``).

Usage:
python scripts/ci/sync_bundle_lfx_pin.py 1.10.0
Expand All @@ -47,7 +47,8 @@
# Matches a bundle's ``"lfx<op>VERSION[,<UPPER]"`` runtime dependency. The
# version operator immediately after ``lfx`` is what distinguishes the runtime
# dep from self-refs like ``"lfx-docling[local]"`` (a ``-`` follows ``lfx``)
# and the nightly ``"lfx-nightly=="`` rename produced by update_bundle_versions.
# and the legacy ``"lfx-nightly=="`` form from the retired nightly bundle
# rename track (see src/bundles/NIGHTLY.md).
_LFX_DEP_PATTERN = re.compile(
r'"lfx(?:>=|~=|==)[\d.]+(?:\.(?:post|dev|a|b|rc)\d+)*'
r'(?:,\s*<[\d.]+(?:\.(?:post|dev|a|b|rc)\d+)*)?"'
Expand Down
181 changes: 0 additions & 181 deletions scripts/ci/update_bundle_versions.py

This file was deleted.

3 changes: 2 additions & 1 deletion src/backend/tests/unit/test_bundle_lfx_pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def test_leaves_self_reference_untouched(self):
assert mod.rewrite_lfx_dep(self_ref, self.FLOOR) == self_ref

def test_leaves_nightly_form_untouched(self):
# update_bundle_versions.py rewrites to this; sync must not clobber it.
# Legacy form from the retired nightly bundle-rename track (see
# src/bundles/NIGHTLY.md); sync must not clobber it if encountered.
assert mod.rewrite_lfx_dep('"lfx-nightly==1.10.0.dev38"', self.FLOOR) == '"lfx-nightly==1.10.0.dev38"'

def test_only_rewrites_runtime_dep_in_full_block(self):
Expand Down
65 changes: 65 additions & 0 deletions src/bundles/lfx-bundles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# lfx-bundles

The long tail of Langflow's provider components as a **single manifest-less
metapackage**, modeled on `langchain-community`. This is the destination for
every vendor/third-party provider that does not warrant its own standalone
distribution; the curated partner providers (OpenAI, Anthropic, AWS,
DataStax, Cohere) ship as separate `lfx-<provider>` packages instead.

## How it works

`lfx-bundles` declares the `lfx.bundles` entry point:

```toml
[project.entry-points."lfx.bundles"]
lfx_bundles = "lfx_bundles"
```

At startup, lfx resolves this package and **folder-walks its immediate
subdirectories**. Each subdirectory is one bundle, registered at the
`@official` slot under its directory name — no `extension.json`, no per-provider
manifest. Adding a provider is just adding a folder.

```
src/lfx_bundles/
├── __init__.py # bare namespace marker
├── <provider>/ # one bundle, e.g. tavily/, pinecone/, ...
│ └── *.py # Component subclasses
└── ...
```

A component's identity is its **bundle name** (`ext:<provider>:<Class>@official`),
which is stable whether the provider ships here or graduates to a standalone
`lfx-<provider>` package. Because a manifest-shipping package always shadows the
manifest-less metapackage, a provider can graduate with **no lockstep release**.

## Installing

Available today (`lfx-bundles` is an empty skeleton until the bulk move
populates it):

```bash
pip install langflow # everything (langflow pins lfx-bundles[all])
pip install lfx # engine only, no bundles
```

Once the long-tail providers move in (and the `lfx[bundles]` extra ships with
the engine-only `lfx` split), these become available:

```bash
pip install "lfx[bundles]" # engine + this metapackage (deployment footnote)
pip install "lfx-bundles[<provider>]" # provider code + that provider's SDK deps
```

`lfx-bundles` itself depends only on `lfx`. Each provider's third-party SDKs are
**optional extras** (PEP 685-normalized keys, e.g. `lfx-bundles[google-genai]`);
the generated `all` extra (empty until the first provider tranche lands) pulls
every provider's deps and is what `langflow` depends on so `pip install langflow`
is unchanged.

## Adding a provider

Providers are moved here by `scripts/migrate/consolidate_bundles.py`, which also
maintains the per-provider extras and the generated `all` aggregate. **Do not**
hand-edit the extras block in `pyproject.toml`. Provider folder names must be
lowercase snake_case (`a-z`, `0-9`, `_`, 2–64 chars).
57 changes: 57 additions & 0 deletions src/bundles/lfx-bundles/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[project]
name = "lfx-bundles"
version = "1.0.0"
description = "Langflow's long-tail provider bundles as a single manifest-less metapackage (the langchain-community model)."
readme = "README.md"
requires-python = ">=3.10,<3.15"
license = { text = "MIT" }
authors = [
{ name = "Langflow", email = "contact@langflow.org" },
]
keywords = ["langflow", "lfx", "extension", "bundle", "providers"]

# Runtime: only lfx (the BUNDLE_API surface). Each provider's third-party SDK
# is an optional extra (see [project.optional-dependencies]); installing
# lfx-bundles bare gives the provider *code* but defers each provider's SDK to
# its extra, so a user opts into exactly the providers they need. The
# generated ``all`` extra pulls every provider's deps and is what ``langflow``
# depends on (``lfx-bundles[all]``) so ``pip install langflow`` stays
# functionally identical to today.
dependencies = [
"lfx>=1.11.0.dev0,<2.0.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR body says this declares an lfx>=1.10.0,<2.0.0 pin, but the code has >=1.11.0.dev0, which I think is the right one (it's what sync_bundle_lfx_pin.py generates for lfx 1.11.0). Could you update the body?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the PR body — it now states the actual lfx>=1.11.0.dev0,<2.0.0 pin (the pre-release-safe floor sync_bundle_lfx_pin.py generates for the 1.11 line) and also mentions the update_bundle_versions.py removal from the other thread.

]

[project.optional-dependencies]
# Per-provider extras + the ``all`` aggregate are populated by the bulk move
# (scripts/migrate/consolidate_bundles.py) as the long-tail providers land
# here. Extra keys are PEP 685-normalized (lowercase, hyphen-separated).
# ``all`` is GENERATED from the per-provider keys -- never hand-edit it.
# Empty until the first provider tranche moves in.
all = []

[project.urls]
Homepage = "https://github.com/langflow-ai/langflow"
Documentation = "https://docs.langflow.org/extensions"
Repository = "https://github.com/langflow-ai/langflow"

# Manifest-less discovery via the ``lfx.bundles`` entry-point group (NOT
# ``langflow.extensions``). The loader (lfx.extension.loader._bundles_root)
# resolves this package with find_spec and folder-walks its immediate
# subdirectories -- each is one bundle at the @official slot, named after the
# directory. No extension.json; exempt from ``lfx extension validate``.
[project.entry-points."lfx.bundles"]
lfx_bundles = "lfx_bundles"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/lfx_bundles"]

[tool.hatch.build.targets.sdist]
include = [
"src/lfx_bundles",
"README.md",
"pyproject.toml",
]
16 changes: 16 additions & 0 deletions src/bundles/lfx-bundles/src/lfx_bundles/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""lfx-bundles: the manifest-less metapackage of Langflow's long-tail providers.
This package is a bare namespace marker. Each immediate subdirectory is one
provider bundle, discovered at runtime by lfx's ``lfx.bundles`` entry-point
folder-walk (``lfx.extension.loader._bundles_root``) and registered at the
``@official`` slot under its directory name. There are intentionally no
re-exports here and no ``extension.json`` -- providers are added as folders,
the langchain-community way.
Provider folders are lowercase snake_case (``BUNDLE_NAME_RE``); a component's
identity is its bundle name (``ext:<provider>:<Class>@official``), stable
whether the provider ships here or in a graduated ``lfx-<provider>`` package.
Providers are added by ``scripts/migrate/consolidate_bundles.py``, never by
hand.
"""
Loading
Loading