From 92162028a641a90d53a02fb394aa5c46c63a9923 Mon Sep 17 00:00:00 2001 From: Jash Gulabrai Date: Wed, 26 Nov 2025 09:48:48 -0500 Subject: [PATCH 1/2] fix: Surface relevant exception when initializing langchain model --- .../llm/models/langchain_initializer.py | 29 ++++++++++++------- .../test_langchain_initialization_methods.py | 6 ++-- .../test_langchain_initializer.py | 29 +++++++++++++++++++ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/nemoguardrails/llm/models/langchain_initializer.py b/nemoguardrails/llm/models/langchain_initializer.py index 6cb937d33..fb58f39b6 100644 --- a/nemoguardrails/llm/models/langchain_initializer.py +++ b/nemoguardrails/llm/models/langchain_initializer.py @@ -225,7 +225,7 @@ def _init_chat_completion_model(model_name: str, provider_name: str, kwargs: Dic raise -def _init_text_completion_model(model_name: str, provider_name: str, kwargs: Dict[str, Any]) -> BaseLLM: +def _init_text_completion_model(model_name: str, provider_name: str, kwargs: Dict[str, Any]) -> BaseLLM | None: """Initialize a text completion model. Args: @@ -234,14 +234,16 @@ def _init_text_completion_model(model_name: str, provider_name: str, kwargs: Dic kwargs: Additional arguments to pass to the model initialization Returns: - An initialized text completion model - - Raises: - RuntimeError: If the provider is not found + An initialized text completion model, or None if the provider is not found """ - provider_cls = _get_text_completion_provider(provider_name) + try: + provider_cls = _get_text_completion_provider(provider_name) + except RuntimeError: + return None + if provider_cls is None: - raise ValueError() + return None + kwargs = _update_model_kwargs(provider_cls, model_name, kwargs) # remove stream_usage parameter as it's not supported by text completion APIs # (e.g., OpenAI's AsyncCompletions.create() doesn't accept this parameter) @@ -249,7 +251,7 @@ def _init_text_completion_model(model_name: str, provider_name: str, kwargs: Dic return provider_cls(**kwargs) -def _init_community_chat_models(model_name: str, provider_name: str, kwargs: Dict[str, Any]) -> BaseChatModel: +def _init_community_chat_models(model_name: str, provider_name: str, kwargs: Dict[str, Any]) -> BaseChatModel | None: """Initialize community chat models. Args: @@ -264,14 +266,19 @@ def _init_community_chat_models(model_name: str, provider_name: str, kwargs: Dic ImportError: If langchain_community is not installed ModelInitializationError: If model initialization fails """ - provider_cls = _get_chat_completion_provider(provider_name) + try: + provider_cls = _get_chat_completion_provider(provider_name) + except RuntimeError: + return None + if provider_cls is None: - raise ValueError() + return None + kwargs = _update_model_kwargs(provider_cls, model_name, kwargs) return provider_cls(**kwargs) -def _init_gpt35_turbo_instruct(model_name: str, provider_name: str, kwargs: Dict[str, Any]) -> BaseLLM: +def _init_gpt35_turbo_instruct(model_name: str, provider_name: str, kwargs: Dict[str, Any]) -> BaseLLM | None: """Initialize GPT-3.5 Turbo Instruct model. Currently init_chat_model from langchain infers this as a chat model. diff --git a/tests/llm_providers/test_langchain_initialization_methods.py b/tests/llm_providers/test_langchain_initialization_methods.py index 14b6f0e0f..6ebdff22e 100644 --- a/tests/llm_providers/test_langchain_initialization_methods.py +++ b/tests/llm_providers/test_langchain_initialization_methods.py @@ -116,8 +116,7 @@ def test_init_community_chat_models_no_provider(self): "nemoguardrails.llm.models.langchain_initializer._get_chat_completion_provider" ) as mock_get_provider: mock_get_provider.return_value = None - with pytest.raises(ValueError): - _init_community_chat_models("community-model", "provider", {}) + assert _init_community_chat_models("community-model", "provider", {}) is None class TestTextCompletionInitializer: @@ -156,8 +155,7 @@ def test_init_text_completion_model_no_provider(self): "nemoguardrails.llm.models.langchain_initializer._get_text_completion_provider" ) as mock_get_provider: mock_get_provider.return_value = None - with pytest.raises(ValueError): - _init_text_completion_model("text-model", "provider", {}) + assert _init_text_completion_model("text-model", "provider", {}) is None class TestUpdateModelKwargs: diff --git a/tests/llm_providers/test_langchain_initializer.py b/tests/llm_providers/test_langchain_initializer.py index a8ff2df34..06c1dae3c 100644 --- a/tests/llm_providers/test_langchain_initializer.py +++ b/tests/llm_providers/test_langchain_initializer.py @@ -194,3 +194,32 @@ def test_text_completion_supports_chat_mode(mock_initializers): mock_initializers["chat"].assert_called_once() mock_initializers["community"].assert_called_once() mock_initializers["text"].assert_called_once() + + +# Tests for error masking prevention (issue where later None returns mask earlier exceptions) + + +def test_exception_not_masked_by_none_return(mock_initializers): + """Test that an exception from an initializer is preserved when later ones return None. + + For example: if community chat throws an error (e.g., invalid API key), but text completion + returns None because that provider type doesn't exist, the community error should be raised. + """ + mock_initializers["special"].return_value = None + mock_initializers["chat"].return_value = None + mock_initializers["community"].side_effect = ValueError("Invalid API key for provider") + mock_initializers["text"].return_value = None # Provider not found, returns None + + with pytest.raises(ModelInitializationError, match="Invalid API key for provider"): + init_langchain_model("community-model", "provider", "chat", {}) + + +def test_import_error_prioritized_over_other_exceptions(mock_initializers): + """Test that ImportError is surfaced to help users know when packages are missing.""" + mock_initializers["special"].return_value = None + mock_initializers["chat"].side_effect = ValueError("Some config error") + mock_initializers["community"].side_effect = ImportError("Missing langchain_community package") + mock_initializers["text"].return_value = None + + with pytest.raises(ModelInitializationError, match="Missing langchain_community package"): + init_langchain_model("model", "provider", "chat", {}) From 8febe4afff70ca0b850752d74da372f734cbbf8e Mon Sep 17 00:00:00 2001 From: Jash Gulabrai Date: Wed, 26 Nov 2025 09:56:54 -0500 Subject: [PATCH 2/2] Remove comment --- tests/llm_providers/test_langchain_initializer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/llm_providers/test_langchain_initializer.py b/tests/llm_providers/test_langchain_initializer.py index 06c1dae3c..f5441ce8f 100644 --- a/tests/llm_providers/test_langchain_initializer.py +++ b/tests/llm_providers/test_langchain_initializer.py @@ -196,9 +196,6 @@ def test_text_completion_supports_chat_mode(mock_initializers): mock_initializers["text"].assert_called_once() -# Tests for error masking prevention (issue where later None returns mask earlier exceptions) - - def test_exception_not_masked_by_none_return(mock_initializers): """Test that an exception from an initializer is preserved when later ones return None.