Skip to content

refactor(llm): framework-owned provider registry#1773

Merged
Pouyanpi merged 2 commits intodevelopfrom
feat/langchain-decouple/stack-7-provider-registry
Apr 16, 2026
Merged

refactor(llm): framework-owned provider registry#1773
Pouyanpi merged 2 commits intodevelopfrom
feat/langchain-decouple/stack-7-provider-registry

Conversation

@Pouyanpi
Copy link
Copy Markdown
Collaborator

@Pouyanpi Pouyanpi commented Apr 7, 2026

Part of the LangChain decoupling stack:

  1. stack-1: canonical types (feat(types): add framework-agnostic LLM type system #1745)
  2. stack-2: adapter and framework registry (feat(llm): add LangChain adapter and framework registry #1759)
  3. stack-3: pipeline rewrite + caller migration (refactor(llm)!: atomic switch to LLMModel protocol #1760)
  4. stack-4: rename generate/stream to generate_async/stream_async (refactor(llm): rename generate/stream to generate_async/stream_async #1769)
  5. stack-5: remove LangChain imports from core modules (refactor(llm): remove LangChain imports from core modules #1770)
  6. stack-6: move LangChain implementations into integrations/langchain/ (refactor(llm): move LangChain implementations into integrations/langchain/ #1772)
  7. stack-7: framework-owned provider registry (this PR)
  8. stack-8: framework-agnostic test infrastructure (TODO)

Description

Add register_provider/get_provider_names to LLMFramework protocol.

our public API delegates to the active framework instead of importing from LangChain internals directly. LangChainFramework implements the new methods, routing to its internal chat/llm provider registries. Backward compat aliases register_chat_provider/register_llm_provider still work.

Summary by CodeRabbit

  • New Features

    • Added provider registration and discovery APIs to the framework, enabling dynamic registration and enumeration of available providers.
  • Deprecations

    • Marked LLM-specific provider functions as deprecated; they will be removed in version 0.23.0. Use the new generic provider APIs instead.

@Pouyanpi Pouyanpi self-assigned this Apr 7, 2026
@Pouyanpi Pouyanpi added this to the v0.22.0 milestone Apr 7, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 7, 2026

Greptile Summary

This PR completes stack-7 of the LangChain decoupling effort by adding register_provider and get_provider_names to the LLMFramework protocol, implementing them in a new LangChainFramework class in llm_adapter.py, and rewiring the public nemoguardrails.llm.providers API to delegate through the active framework rather than importing LangChain internals directly. A follow-up fix commit keeps the separate chat/LLM provider registries intact and adds DeprecationWarning to register_llm_provider and get_llm_provider_names, addressing the previously noted concerns about merged lists and backward compatibility.

Confidence Score: 5/5

Safe to merge; the refactor is well-structured, previous P1 concerns are resolved, and the only remaining finding is a P2 test helper gap.

All prior P1/P0 issues (merged provider lists, compat alias routing, hasattr guards) were addressed in this PR or its follow-up fix commit. The single remaining finding — FakeFramework not implementing the full protocol — is P2: existing tests pass and there is no user-facing regression.

tests/llm/test_frameworks.py — FakeFramework should be updated to implement the two new protocol methods.

Important Files Changed

Filename Overview
nemoguardrails/types.py Added register_provider and get_provider_names to the LLMFramework @runtime_checkable Protocol; tests in test_types.py correctly verify both satisfaction and failure paths.
nemoguardrails/llm/providers/init.py Public API rewired to delegate to the active framework; deprecated wrappers retain hasattr guards and emit DeprecationWarning; get_chat_provider_names and get_llm_provider_names now return distinct lists via LangChain-specific passthrough.
nemoguardrails/integrations/langchain/llm_adapter.py New LangChainFramework class added; register_provider routes unconditionally to _register_chat (chat-first design); register_llm_provider/get_chat_provider_names/get_llm_provider_names kept as backward-compat passthrough to internal provider registries.
nemoguardrails/cli/providers.py Wraps get_llm_provider_names calls in warnings.catch_warnings to suppress the new deprecation warning; CLI continues to render chat and text-completion lists as separate sections.
tests/llm/test_frameworks.py FakeFramework only implements create_model, leaving it protocol-incomplete after register_provider and get_provider_names became required; existing tests pass because no provider operations are called on it, but it misrepresents the new protocol contract.
tests/test_types.py Added TestLLMFrameworkProtocol that correctly asserts a full three-method MockFramework satisfies the protocol and that an empty class fails the isinstance check.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Public API\nnemoguardrails.llm.providers"] -->|register_provider / get_provider_names| B["_active_framework()\n→ LangChainFramework"]
    A -->|register_chat_provider| B
    A -->|register_llm_provider\n⚠️ DeprecationWarning| C{hasattr register_llm_provider?}
    A -->|get_chat_provider_names| D{hasattr get_chat_provider_names?}
    A -->|get_llm_provider_names\n⚠️ DeprecationWarning| E{hasattr get_llm_provider_names?}

    B -->|register_provider| F["_register_chat()\n→ _chat_providers dict"]
    B -->|register_llm_provider| G["_register_llm()\n→ _llm_providers dict\n(validates _acall)"]
    B -->|get_provider_names| H["sorted union\nchat ∪ llm names"]
    B -->|get_chat_provider_names| I["_chat_providers names\n+ partner providers"]
    B -->|get_llm_provider_names| J["_llm_providers names"]

    C -->|yes| G
    C -->|no| F
    D -->|yes| I
    D -->|no| H
    E -->|yes| J
    E -->|no| H
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: tests/llm/test_frameworks.py
Line: 46-49

Comment:
**`FakeFramework` doesn't satisfy the updated `LLMFramework` protocol**

`FakeFramework` only implements `create_model`, but `LLMFramework` now requires `register_provider` and `get_provider_names` as well (added in `types.py` in this PR). `isinstance(FakeFramework(), LLMFramework)` returns `False`. The existing `TestRegistry` tests still pass because none of them call provider operations on `FakeFramework`, but `test_set_and_get_default_framework` sets it as the active default framework — any provider call inside that window would raise `AttributeError`. `TestLLMFrameworkProtocol` in `test_types.py` already verifies the protocol shape; keeping `FakeFramework` complete here avoids silent failures for future test additions.

```suggestion
class FakeFramework:
    def create_model(self, model_name, provider_name, model_kwargs=None):
        return MagicMock(spec=LLMModel)

    def register_provider(self, name, provider_cls):
        pass

    def get_provider_names(self):
        return []
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (10): Last reviewed commit: "fix(llm): keep separate chat/llm provide..." | Re-trigger Greptile

Comment thread nemoguardrails/llm/providers/__init__.py Outdated
Comment thread nemoguardrails/llm/providers/__init__.py Outdated
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 94.11765% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
nemoguardrails/llm/providers/__init__.py 89.28% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Pouyanpi Pouyanpi force-pushed the feat/langchain-decouple/stack-7-provider-registry branch 2 times, most recently from aca95ea to db03710 Compare April 8, 2026 10:38
Comment thread nemoguardrails/llm/providers/__init__.py
Comment thread nemoguardrails/llm/providers/__init__.py
@Pouyanpi Pouyanpi force-pushed the refactor/langchain-decouple/stack-6-move-providers branch from e55fbcd to b781f72 Compare April 16, 2026 08:36
Base automatically changed from refactor/langchain-decouple/stack-6-move-providers to develop April 16, 2026 08:46
Add register_provider/get_provider_names to LLMFramework protocol.
Public API (llm/providers/__init__.py) delegates to the active
framework instead of importing from LangChain internals directly.
LangChainFramework implements the new methods, routing to its
internal chat/llm provider registries. Backwards-compat aliases
register_chat_provider/register_llm_provider still work.
…ings

LangChainFramework exposes get_chat_provider_names() and
get_llm_provider_names() separately so CLI and existing tests work.
register_llm_provider and get_llm_provider_names emit DeprecationWarning
(removal in 0.23.0). register_provider always targets chat registry.
Add 7 provider registration tests with cleanup fixture to prevent
polluting the global LangChain provider dicts.
@Pouyanpi Pouyanpi force-pushed the feat/langchain-decouple/stack-7-provider-registry branch from cfe3d8d to af30ae5 Compare April 16, 2026 08:50
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a framework-driven provider management system. The LLMFramework protocol is extended with register_provider and get_provider_names methods, implemented in LangChainFramework. The main provider API is refactored to delegate through the active framework, with deprecated LLM-specific methods now emitting warnings. CLI integration suppresses these warnings where appropriate.

Changes

Cohort / File(s) Summary
Framework Protocol & Implementation
nemoguardrails/types.py, nemoguardrails/integrations/langchain/llm_adapter.py
Extended LLMFramework protocol with register_provider and get_provider_names methods. Implemented both methods in LangChainFramework, along with framework-specific registration helpers that delegate to provider registries.
Provider API Refactoring
nemoguardrails/llm/providers/__init__.py
Restructured provider management to use active framework delegation. Added generic register_provider and get_provider_names functions with framework fallback. Marked register_llm_provider and get_llm_provider_names as deprecated (emitting warnings). Removed get_community_chat_provider_names from exports.
CLI Integration
nemoguardrails/cli/providers.py
Added deprecation warning suppression when calling get_llm_provider_names in text completion provider listing.
Test Coverage
tests/llm/test_frameworks.py, tests/test_types.py
Added comprehensive test cases for provider registration and discovery APIs, including test doubles (FakeChatProvider, FakeLLMProvider), provider cleanup fixtures, and verification of deprecation warnings. Updated MockFramework in protocol test to satisfy extended interface.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI
    participant Provider API
    participant LLMFramework
    participant LangChainFramework as LangChain<br/>Framework
    participant ProviderRegistry

    User->>CLI: List providers (text completion)
    CLI->>Provider API: get_llm_provider_names()
    note over Provider API: Emit DeprecationWarning
    Provider API->>LLMFramework: get_framework()
    LLMFramework->>LangChainFramework: get_llm_provider_names()
    LangChainFramework->>ProviderRegistry: Query LLM providers
    ProviderRegistry-->>LangChainFramework: Provider names list
    LangChainFramework-->>Provider API: Names
    Provider API-->>CLI: Names (warning suppressed)
    CLI-->>User: Display provider list

    User->>CLI: Register new provider
    CLI->>Provider API: register_provider(name, cls)
    Provider API->>LLMFramework: get_framework()
    LLMFramework->>LangChainFramework: register_provider(name, cls)
    LangChainFramework->>ProviderRegistry: Store provider
    ProviderRegistry-->>LangChainFramework: Stored
    LangChainFramework-->>Provider API: Success
    Provider API-->>CLI: Confirmed
    CLI-->>User: Provider registered
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Results For Major Changes ⚠️ Warning PR contains major changes (protocol refactoring, API changes, deprecation warnings) but PR description lacks test results or testing information documentation. Add test execution results, test coverage metrics, and verification that existing tests still pass to the PR description.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the primary change: moving provider registry responsibility to the LLM framework layer rather than relying on LangChain internals directly.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/langchain-decouple/stack-7-provider-registry

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
nemoguardrails/llm/providers/__init__.py (1)

22-23: Add a concrete return type to _active_framework for stronger static checks.

A return annotation improves readability/type-safety for all delegated calls in this module.

♻️ Suggested typing tweak
 from typing import Any, List
@@
+from nemoguardrails.types import LLMFramework
@@
-def _active_framework():
+def _active_framework() -> LLMFramework:
     return get_framework(get_default_framework())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoguardrails/llm/providers/__init__.py` around lines 22 - 23, The helper
function _active_framework currently has no return annotation; add a concrete
return type to improve static checking by annotating _active_framework with the
framework interface/type returned by get_framework (e.g., import and use the
exported Framework/FrameworkProtocol type from the module that defines
get_framework) and keep the implementation as return
get_framework(get_default_framework()) so callers of _active_framework have a
precise type.
tests/llm/test_frameworks.py (1)

129-145: Consider a local helper/context manager for deprecation-warning suppression in tests.

You repeat the same warning-suppression pattern several times; consolidating it will reduce noise in this test class.

♻️ Suggested test helper pattern
+from contextlib import contextmanager
+
+@contextmanager
+def _ignore_deprecated_llm_provider_warning():
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        yield
@@
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore", DeprecationWarning)
+        with _ignore_deprecated_llm_provider_warning():
             register_llm_provider("test_llm", FakeLLMProvider)
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore", DeprecationWarning)
+        with _ignore_deprecated_llm_provider_warning():
             assert "test_llm" in get_llm_provider_names()

Also applies to: 154-156

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/llm/test_frameworks.py` around lines 129 - 145, Create a small local
context manager (e.g., suppress_deprecation_warnings) in
tests/llm/test_frameworks.py that uses warnings.catch_warnings() and
warnings.simplefilter("ignore", DeprecationWarning), then replace each repeated
with warnings.catch_warnings(): ... block around calls to register_llm_provider,
register_chat_provider, get_llm_provider_names, and get_chat_provider_names with
a single with suppress_deprecation_warnings(): wrapper; apply the same
replacement for the other occurrences mentioned (the later get_* calls).
nemoguardrails/cli/providers.py (1)

35-42: Extract deprecated LLM-provider lookup into one helper.

The behavior is correct, but the warning-suppression block is duplicated. A tiny helper will keep this consistent and easier to update.

♻️ Suggested refactor
+def _get_llm_provider_names_silenced() -> List[str]:
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        return sorted(get_llm_provider_names())
+
 def _list_providers() -> None:
@@
-    with warnings.catch_warnings():
-        warnings.simplefilter("ignore", DeprecationWarning)
-        console.print("\n[bold]Text Completion Providers:[/]")
-        for provider in sorted(get_llm_provider_names()):
-            console.print(f"  • {provider}")
+    console.print("\n[bold]Text Completion Providers:[/]")
+    for provider in _get_llm_provider_names_silenced():
+        console.print(f"  • {provider}")
@@
 def _get_provider_completions(
     provider_type: Optional[ProviderType] = None,
 ) -> List[str]:
@@
     if provider_type == "text completion":
-        # See comment in _list_providers for why we suppress this warning.
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore", DeprecationWarning)
-            return sorted(get_llm_provider_names())
+        return _get_llm_provider_names_silenced()

Also applies to: 54-57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoguardrails/cli/providers.py` around lines 35 - 42, Extract the duplicated
warning-suppression + printing logic into a small helper (e.g.,
_print_providers_suppressing_deprecation) that accepts a heading string and a
provider-getter function; inside the helper use warnings.catch_warnings() with
warnings.simplefilter("ignore", DeprecationWarning) and print the heading then
iterate sorted(provider_getter()) to print each provider. Replace the two
duplicated blocks that call get_llm_provider_names() and
get_embedding_provider_names() with calls to this helper (keep console.print
formatting identical).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@nemoguardrails/cli/providers.py`:
- Around line 35-42: Extract the duplicated warning-suppression + printing logic
into a small helper (e.g., _print_providers_suppressing_deprecation) that
accepts a heading string and a provider-getter function; inside the helper use
warnings.catch_warnings() with warnings.simplefilter("ignore",
DeprecationWarning) and print the heading then iterate sorted(provider_getter())
to print each provider. Replace the two duplicated blocks that call
get_llm_provider_names() and get_embedding_provider_names() with calls to this
helper (keep console.print formatting identical).

In `@nemoguardrails/llm/providers/__init__.py`:
- Around line 22-23: The helper function _active_framework currently has no
return annotation; add a concrete return type to improve static checking by
annotating _active_framework with the framework interface/type returned by
get_framework (e.g., import and use the exported Framework/FrameworkProtocol
type from the module that defines get_framework) and keep the implementation as
return get_framework(get_default_framework()) so callers of _active_framework
have a precise type.

In `@tests/llm/test_frameworks.py`:
- Around line 129-145: Create a small local context manager (e.g.,
suppress_deprecation_warnings) in tests/llm/test_frameworks.py that uses
warnings.catch_warnings() and warnings.simplefilter("ignore",
DeprecationWarning), then replace each repeated with warnings.catch_warnings():
... block around calls to register_llm_provider, register_chat_provider,
get_llm_provider_names, and get_chat_provider_names with a single with
suppress_deprecation_warnings(): wrapper; apply the same replacement for the
other occurrences mentioned (the later get_* calls).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ff883f7-0fb0-4212-ac5f-06dd4e8f2b91

📥 Commits

Reviewing files that changed from the base of the PR and between c2319b0 and af30ae5.

📒 Files selected for processing (6)
  • nemoguardrails/cli/providers.py
  • nemoguardrails/integrations/langchain/llm_adapter.py
  • nemoguardrails/llm/providers/__init__.py
  • nemoguardrails/types.py
  • tests/llm/test_frameworks.py
  • tests/test_types.py

@Pouyanpi Pouyanpi merged commit 5c2c1ea into develop Apr 16, 2026
7 checks passed
@Pouyanpi Pouyanpi deleted the feat/langchain-decouple/stack-7-provider-registry branch April 16, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants