Skip to content

feat(lfx): let extension bundles register model providers#13916

Merged
erichare merged 9 commits into
release-1.11.0from
feat/model-provider-bundles
Jun 30, 2026
Merged

feat(lfx): let extension bundles register model providers#13916
erichare merged 9 commits into
release-1.11.0from
feat/model-provider-bundles

Conversation

@erichare

@erichare erichare commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

What & why

Model providers are hardcoded across lfx core (MODEL_PROVIDER_METADATA, the class-import registries, LIVE_MODEL_PROVIDERS, and the credential / instantiation / live-discovery dispatch), so a third party cannot add a provider without editing core — adding vLLM (#13910) touched 12 files. This PR introduces a supported extension point so an extension bundle can register a model provider declaratively, with zero core edits and byte-identical behavior when no provider bundle is installed.

This is PR 1 of 2:

  1. (this PR) the core "providers from bundles" seam.
  2. next: lfx-vllm, a standalone provider bundle that rebuilds feat: add vLLM as a first-class Model Provider with dynamic model discovery #13910 on this seam, attributed to the original author — superseding feat: add vLLM as a first-class Model Provider with dynamic model discovery #13910.

How

  • lfx/base/models/provider_registry.py (new): ProviderSpec + register_provider, which merges a provider into the core tables in place. Every existing accessor — the @lru_cached variable maps, the shared model_provider_metadata reference, the /api/v1/models surface — then sees the provider with no edits. Core providers win on name collision; live-discovery and validator callables resolve lazily by dotted path; clear() is a test seam.
  • Manifest (extension.json): new declarative providers[] block; a provider-only extension may ship no component bundle (empty bundles); an extension must declare at least one bundle or provider.
  • Loader: load_extension registers manifest providers at the startup chokepoint, failure-isolated (a bad provider is a warning, never a load abort).
  • Dispatch seams made registry-aware: get_model_providers union, live-model discovery fallback, credential-validation fallback, api-key-optional check.

Author contract

A bundle declares, per provider: name + metadata (mirrors a MODEL_PROVIDER_METADATA value), an optional lazy model_class / embedding, api_key_required, live / conditional_live, and dotted-path live_discovery / validator callables.

Back-compat & safety

  • _CORE tables keep today's exact values; the merge is identity when nothing is registered → no behavior change, no new imports.
  • Lazy imports preserved (class paths stored as strings, imported only at instantiation).
  • A broken provider bundle degrades to "provider absent / static-only" — it never breaks the core model system.

Tests

  • test_provider_registry.py — register / precedence / clear(), api-key-optional, live-discovery + validator dispatch, embedding wiring, baseline restoration, invalid specs.
  • test_provider_extension.py — end-to-end through the real loader (a provider-only extension appears in get_model_providers, live discovery dispatches) plus manifest-schema rules.
  • Full tests/unit/extension + tests/unit/base/models suites pass (721) under the new contract.

Refs #13910

Summary by CodeRabbit

  • New Features

    • Extensions can now contribute model providers directly, including provider-only extensions without a bundle.
    • Newly added providers appear in model provider listings and can support optional API keys and live model discovery.
  • Bug Fixes

    • Improved handling of provider validation and discovery so unsupported or failing providers no longer break loading; they now fail gracefully.
    • Provider registration now better preserves existing built-in behavior while allowing compatible extension additions.

Model providers were hardcoded across lfx core (MODEL_PROVIDER_METADATA, the
class-import registries, LIVE_MODEL_PROVIDERS, and the credential /
instantiation / live-discovery dispatch), so a third party could not add a
provider without editing core -- adding vLLM (#13910) touched 12 files.

Introduce a supported extension point: an extension manifest may declare a
`providers[]` block, which the loader merges into the core provider tables in
place via a new `provider_registry`. Every existing accessor (the lru_cached
variable maps, the shared `model_provider_metadata` reference, the
/api/v1/models surface) then sees a registered provider with no further edits.

- new lfx/base/models/provider_registry.py: ProviderSpec + register_provider
  with core-wins precedence, failure isolation, and a clear() test seam;
  live-discovery and validator callables resolved lazily by dotted path.
- manifest: declarative providers[]; allow a provider-only extension (empty
  bundles); require at least one bundle or provider.
- loader: register manifest providers during load_extension (the startup
  chokepoint), failure-isolated as warnings.
- dispatch seams made registry-aware: get_model_providers union, live-model
  discovery fallback, credential-validation fallback, api-key-optional check.
- zero added cost and byte-identical behavior when no provider bundle is
  installed.

Groundwork for shipping providers as standalone bundles (e.g. lfx-vllm,
superseding #13910).
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 23e68a00-2c7a-43e3-b24c-e579ee0c888a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a new provider_registry module enabling extension bundles to register model providers, including metadata, embedding wiring, and live-discovery/validation callables. Wires this registry into live model discovery, credential validation, provider listing, and LLM/embeddings instantiation. Extends the extension manifest schema and loader to support provider-only extensions, plus adds corresponding tests.

Changes

Bundle Provider Registry Support

Layer / File(s) Summary
Provider registry core implementation
src/lfx/src/lfx/base/models/provider_registry.py
New module defining ProviderSpec, register_provider(), lookup helpers (is_registered, is_api_key_optional, live_discovery_for, validator_for, registered_provider_names), and a clear() test seam that reverses in-place mutations.
Wiring registry into model discovery, validation, instantiation
src/lfx/src/lfx/base/models/model_utils.py, src/lfx/src/lfx/base/models/unified_models/credentials.py, src/lfx/src/lfx/base/models/unified_models/provider_queries.py, src/lfx/src/lfx/base/models/unified_models/instantiation.py
Live model discovery falls back to registry-provided discovery callables; credential validation dispatches to registered bundle validators; get_model_providers() unions catalog and metadata providers; API-key-required checks honor is_api_key_optional().
Manifest schema and extension loader provider support
src/lfx/src/lfx/extension/manifest.py, src/lfx/src/lfx/extension/loader/_orchestrator.py
New ProviderClassRef, ProviderEmbeddingRef, ProviderManifestEntry models with validation; bundles relaxed to allow zero entries; new providers field with uniqueness/at-least-one validators; loader registers manifest providers and supports provider-only extensions with early return.
Registry and manifest test coverage
src/lfx/tests/unit/base/models/test_provider_registry.py, src/lfx/tests/unit/extension/test_provider_extension.py, src/lfx/tests/unit/extension/test_manifest.py, src/lfx/tests/unit/extension/test_schema.py
New unit tests for registry registration/dispatch/clear semantics; new integration tests for provider-only extension loading; updated malformed-manifest test cases reflecting relaxed bundle constraint.

Sequence Diagram(s)

sequenceDiagram
  participant Extension as Extension Manifest
  participant Loader as load_extension
  participant Registry as provider_registry
  participant ModelUtils as model_utils / credentials

  Extension->>Loader: providers[] declared in manifest
  Loader->>Registry: register_provider(spec) per provider
  Registry->>Registry: validate, merge metadata/class/mapping tables
  Registry-->>Loader: success or provider-invalid warning
  Loader-->>Loader: continue or return early (provider-only)
  ModelUtils->>Registry: live_discovery_for(provider) / validator_for(provider)
  Registry-->>ModelUtils: callable or None
  ModelUtils->>ModelUtils: invoke callable, return result or []
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • langflow-ai/langflow#13641: Builds on the same live model discovery and provider registry mechanisms introduced here for listing/defaulting installed local tool-calling models.

Suggested labels

test

Suggested reviewers

  • dkaushik94
  • mendonk
  • Cristhianzl
🚥 Pre-merge checks | ✅ 7 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Quality And Coverage ⚠️ Warning Tests are broad but miss key new paths: the loader warning test never reaches _register_manifest_providers, and optional API-key instantiation isn't exercised. Add a schema-valid provider-registration failure that asserts provider-invalid/provider-skipped, plus regression tests for get_llm/get_embeddings with api_key_required=False.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: extension bundles can now register model providers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations ✅ Passed Yes—new unit and integration tests were added and updated to cover provider registry, loader, manifest, and schema behavior, all with proper test_*.py naming.
Test File Naming And Structure ✅ Passed The added tests use repo-standard test_*.py names, clear pytest functions, fixtures, and logical unit/integration placement with positive and negative cases.
Excessive Mock Usage Warning ✅ Passed Tests exercise real registry/loader/manifest paths and use only simple local helper callables; no Mock/patch/monkeypatch usage was found.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/model-provider-bundles

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the enhancement New feature or request label Jun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.01887% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.78%. Comparing base (fa78cdd) to head (467e4ff).
⚠️ Report is 25 commits behind head on release-1.11.0.

Files with missing lines Patch % Lines
src/lfx/src/lfx/base/models/provider_registry.py 78.80% 24 Missing and 15 partials ⚠️
...rc/lfx/base/models/unified_models/instantiation.py 60.52% 7 Missing and 8 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##           release-1.11.0   #13916      +/-   ##
==================================================
- Coverage           59.58%   58.78%   -0.80%     
==================================================
  Files                2349     2351       +2     
  Lines              224820   225218     +398     
  Branches            33523    33581      +58     
==================================================
- Hits               133954   132403    -1551     
- Misses              89347    91268    +1921     
- Partials             1519     1547      +28     
Flag Coverage Δ
lfx 56.76% <83.01%> (+0.29%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/lfx/src/lfx/base/models/model_utils.py 67.68% <100.00%> (+1.86%) ⬆️
.../src/lfx/base/models/unified_models/credentials.py 45.82% <100.00%> (+2.40%) ⬆️
...lfx/base/models/unified_models/provider_queries.py 78.78% <100.00%> (+0.66%) ⬆️
src/lfx/src/lfx/extension/errors.py 88.00% <ø> (ø)
src/lfx/src/lfx/extension/loader/_orchestrator.py 82.27% <100.00%> (+1.29%) ⬆️
src/lfx/src/lfx/extension/manifest.py 90.05% <100.00%> (+3.72%) ⬆️
...rc/lfx/base/models/unified_models/instantiation.py 52.58% <60.52%> (+11.19%) ⬆️
src/lfx/src/lfx/base/models/provider_registry.py 78.80% <78.80%> (ø)

... and 177 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lfx/src/lfx/base/models/model_utils.py`:
- Around line 718-724: The live discovery path in model_utils.py returns
extension output from discovery(user_id, model_type) directly, so invalid values
like None or non-list results can leak past the list[dict] contract. Update the
live-discovery handling in the function that calls live_discovery_for to
validate the returned value and only accept a list; if the callable returns
anything else, treat it like a failed discovery by logging the debug message and
returning an empty list, just as the existing exception path does.

In `@src/lfx/src/lfx/base/models/provider_registry.py`:
- Around line 254-261: _resolve_callable() should fail softly for malformed or
non-callable extension references instead of letting import-time exceptions
break discovery. Update the provider loading/validation path around
_resolve_callable and the caller in the provider registry to catch broader
import/lookup failures such as SyntaxError and other runtime exceptions from
importlib.import_module, then verify the resolved attribute is actually callable
before accepting it. If resolution fails, log/record it and return None so bad
extensions stay isolated and do not stop registry validation.
- Around line 154-156: Validate provider metadata keys in ProviderRegistry
before any registration writes happen: in the spec validation path that checks
spec.model_class_name and in the registration block that mutates
_MODEL_CLASS_IMPORTS and EMBEDDING_PARAM_MAPPINGS, reject specs whose
lazy-import class key is missing, already claimed, or not backed by either an
existing import entry or spec.model_class. Also guard embedding_param_key
against overwriting an existing mapping, including core mappings, so clear()
cannot remove a preexisting entry; perform these checks under the registry lock
before updating the global tables.

In `@src/lfx/src/lfx/base/models/unified_models/credentials.py`:
- Around line 421-425: The provider validation path in
is_registered/validator_for currently lets bundle validator exceptions escape as
non-ValueError, breaking the function’s expected user-facing contract. Wrap the
bundle_validator(provider, variables, validation_model) call in the
registered-provider branch so any transport/client or validator failure is
caught and normalized into a ValueError, while preserving the existing early
return behavior for successful validation.

In `@src/lfx/src/lfx/base/models/unified_models/instantiation.py`:
- Around line 101-107: `get_llm` is still forwarding an `api_key` kwarg even for
providers marked optional via `is_api_key_optional`, unlike `get_embeddings`.
Update the LLM instantiation path in `instantiation.py` so optional providers
omit the API-key argument entirely when no key is resolved, and keep the
existing guard for required providers. Add a regression test around `get_llm`
that instantiates an `api_key_required=False` provider and verifies no `api_key`
is passed.

In `@src/lfx/src/lfx/extension/loader/_orchestrator.py`:
- Around line 279-300: The provider registration loop in the orchestrator is
ignoring the False result from register_provider(), so manifest providers that
collide with built-ins or already-registered extensions are silently dropped.
Update the loader logic around manifest.providers and register_provider() to
detect a failed registration, record that provider as skipped in the returned
LoadResult (using the existing result structure in the loader/orchestrator
flow), and surface enough context to identify the conflicting provider name.

In `@src/lfx/tests/unit/extension/test_provider_extension.py`:
- Around line 112-129: The current test name implies it verifies provider
registration failures are downgraded to warnings, but removing
metadata.mapping.model_class causes ExtensionManifest.model_validate() to fail
first, so it only checks manifest-invalid. Update
test_invalid_provider_is_warning_not_fatal in test_provider_extension.py to
either rename it to reflect the manifest validation failure or, preferably, add
a schema-valid manifest that reaches _register_manifest_providers() and triggers
the provider-invalid path in _orchestrator, then assert result.warnings and that
provider_registry remains unregistered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11a136e7-09fe-4b81-aed7-c4c67735f0e7

📥 Commits

Reviewing files that changed from the base of the PR and between cfb1074 and 597428f.

📒 Files selected for processing (11)
  • src/lfx/src/lfx/base/models/model_utils.py
  • src/lfx/src/lfx/base/models/provider_registry.py
  • src/lfx/src/lfx/base/models/unified_models/credentials.py
  • src/lfx/src/lfx/base/models/unified_models/instantiation.py
  • src/lfx/src/lfx/base/models/unified_models/provider_queries.py
  • src/lfx/src/lfx/extension/loader/_orchestrator.py
  • src/lfx/src/lfx/extension/manifest.py
  • src/lfx/tests/unit/base/models/test_provider_registry.py
  • src/lfx/tests/unit/extension/test_manifest.py
  • src/lfx/tests/unit/extension/test_provider_extension.py
  • src/lfx/tests/unit/extension/test_schema.py

Comment thread src/lfx/src/lfx/base/models/model_utils.py
Comment thread src/lfx/src/lfx/base/models/provider_registry.py
Comment thread src/lfx/src/lfx/base/models/provider_registry.py Outdated
Comment thread src/lfx/src/lfx/base/models/unified_models/credentials.py
Comment thread src/lfx/src/lfx/base/models/unified_models/instantiation.py
Comment thread src/lfx/src/lfx/extension/loader/_orchestrator.py Outdated
Comment thread src/lfx/tests/unit/extension/test_provider_extension.py Outdated
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 44%
44.51% (60138/135093) 69.29% (8176/11799) 42.76% (1356/3171)

Unit Test Results

Tests Skipped Failures Errors Time
5097 0 💤 0 ❌ 0 🔥 13m 38s ⏱️

get_llm/get_embeddings resolved base_url (and attribution headers) only via
hardcoded per-provider branches, so a bundle-contributed OpenAI-compatible
provider could not point its client at a custom endpoint -- it would silently
hit the default API. Add a generic, is_registered-gated step that applies each
non-secret metadata variable to its declared langchain_param (base_url
localhost-rewritten) or HTTP header, mirroring the core branches. Core
providers keep their explicit branches, so behavior is unchanged.

Completes the provider-bundle seam so an OpenAI-compatible provider (e.g. vLLM)
works fully from a bundle with zero core edits.
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
…viders

langchain_openai's ChatOpenAI / OpenAIEmbeddings raise when constructed with
api_key=None, so an OpenAI-compatible provider that declared
api_key_required=False (e.g. a local vLLM server without auth) would fail to
instantiate even though the model system correctly skips the "API key required"
error. Pass a non-empty "EMPTY" placeholder for such providers so the client
constructs. Only applies to registered (bundle) providers that opted out of
API keys; core providers are unaffected.
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
…oviders

get_embeddings only passed an API key when the provider's param_mapping
declared an explicit "api_key" slot. Bundle providers can omit that slot, so
apply the resolved key (or the api-key-optional placeholder) under the
OpenAI-compatible "api_key" kwarg for registered providers -- mirroring how
get_llm already resolves the key. Lets an OpenAI-compatible embedding provider
(e.g. vLLM) work from a bundle without that slot.
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
…13910 (#13919)

feat(bundles): add vLLM model-provider bundle (lfx-vllm)

Ships vLLM as a standalone Langflow Extension Bundle built on the
provider-registry seam: its extension.json declares a providers[] entry that
registers a vLLM model provider into the unified model system, with zero core
edits. vLLM is OpenAI-compatible, so the provider reuses ChatOpenAI /
OpenAIEmbeddings and discovers served models live from /v1/models (SSRF-guarded);
the same model is offered for both Language Model and Embedding Model use, and
no API key is required for local servers.

Inherits the original vLLM provider from #13910 by Yash Pareek (@pareek-ml),
reconstructed as a bundle so it no longer edits the 12 core files that PR did.

Co-authored-by: Yash Pareek <yash.pareek@usi.ch>
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
erichare added 2 commits June 30, 2026 14:36
- model_utils: normalize non-list live-discovery returns to [] (list[dict] contract)
- provider_registry: validate lazy-import/embedding keys before mutating any
  global table (reject unknown or conflicting model/embedding classes and
  embedding_param_key collisions, so clear() can't drop a core mapping);
  _resolve_callable verifies the target is callable; callable resolution now
  catches broad import-time failures (SyntaxError, side effects) and degrades
  to None
- credentials: normalize non-ValueError bundle-validator failures to ValueError
  so they can't escape as an unhandled 500
- loader: surface skipped (name-collision) provider registrations as a typed
  provider-skipped warning instead of returning silent success
- errors: register provider-invalid / provider-skipped codes + format templates
- tests: cover the register-time warning-isolation and collision paths,
  unknown/conflicting keys, non-callable and non-list discovery, and validator
  normalization
Manifest providers[] (model-provider registration) and the optional
0-or-1 bundles[] (provider-only extensions) touch in-scope BUNDLE_API
files (manifest.py, _orchestrator.py, errors.py); record the additive
contract change + the new provider-invalid / provider-skipped codes so
the changelog gate passes. No BUNDLE_API_VERSION bump (all additive).
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
Addresses two review findings on the provider seam:

- [P1] get_model_provider_variable_mapping() fell back to the FIRST provider
  variable when no *required* secret existed, so a provider whose API key is an
  optional secret (e.g. vLLM's VLLM_API_KEY) mapped to its required non-secret
  base URL. get_api_key_for_provider then resolved and forwarded the endpoint as
  the bearer token, skipping the "EMPTY" placeholder. Now prefer a required
  secret, then any secret, then the first variable. Covered by a test that
  exercises the real resolver (no patched stub).
- [P2] discover_installed_extensions / discover_seed_extensions indexed
  manifest.bundles[0], crashing with IndexError on a valid provider-only
  manifest (bundles=[]) and breaking `lfx extension list` / registry discovery.
  Guard the empty-bundle case; DiscoveredExtension.bundle_name and
  registry.Extension.bundle_name are now str | None, and the list CLI renders a
  dash for provider-only extensions.

BUNDLE_API.md changelog updated for the discovery/registry surface nullability.
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jun 30, 2026
@erichare erichare merged commit c7d7676 into release-1.11.0 Jun 30, 2026
54 checks passed
@erichare erichare deleted the feat/model-provider-bundles branch June 30, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant