Skip to content

fix(content_safety): clear error when Azure model init fails#1927

Open
adityasingh2400 wants to merge 1 commit into
NVIDIA-NeMo:developfrom
adityasingh2400:adityasingh2400/content-safety-init-validation
Open

fix(content_safety): clear error when Azure model init fails#1927
adityasingh2400 wants to merge 1 commit into
NVIDIA-NeMo:developfrom
adityasingh2400:adityasingh2400/content-safety-init-validation

Conversation

@adityasingh2400
Copy link
Copy Markdown

@adityasingh2400 adityasingh2400 commented May 23, 2026

Closes #1338.

When a content_safety rail is configured against an Azure model that fails to initialize because of a missing or blank api_key (or deployment_name), the rail later tried to call .create on a None client and surfaced as LLMCallException: 'NoneType' object has no attribute 'create'. This change validates the model init at configuration time and raises a clear error pointing at the missing field, so users can fix the config without diffing tracebacks.

The fix has three pieces. The LangChain initializer now verifies that the wrapper returned by init_chat_model has a usable client; when an older provider integration swallows credential errors and returns a half-built wrapper whose client attribute is None, we surface that as a ModelInitializationError naming the model, provider, and the credential-shaped kwargs that were missing or empty. The content_safety actions report which models actually loaded and call out the most common configuration causes when the requested model is absent. Finally, llm_call recognizes the NoneType attribute error pattern and prepends an init-time hint to the LLMCallException, so any remaining regression of this class still produces a configuration-shaped message instead of a bare attribute error.

Added regression tests covering the half-built Azure wrapper, the content_safety check input and output paths when the model is missing, and the llm_call exception enrichment when a None client AttributeError is raised. The happy path is unchanged.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error messages when LLM models are misconfigured with missing SDK clients, now including available configuration context for easier troubleshooting.
    • Enhanced error reporting for content safety model initialization failures with clearer guidance on missing credential parameters.
  • Tests

    • Added regression tests for LLM client validation and content safety model initialization scenarios.

Review Change Stack

…VIDIA-NeMo#1338)

When a content_safety rail is configured against an Azure model that fails to initialize because of a missing or blank api_key (or deployment_name), the rail later tried to call .create on a None client and surfaced as LLMCallException: 'NoneType' object has no attribute 'create'. This change validates the model init at configuration time and raises a clear error pointing at the missing field, so users can fix the config without diffing tracebacks.

Signed-off-by: Aditya Singh <adisin650@gmail.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

Walkthrough

The PR improves error detection and messaging when LLM model initialization or execution fails due to missing underlying SDK clients, typically caused by invalid or missing credentials. It adds a proactive initialization check in LangChain model setup and a reactive runtime error enrichment in the LLM call path, along with improved error context in content-safety actions.

Changes

LLM Client Validation and Error Enrichment

Layer / File(s) Summary
LLM call error detection and enrichment
nemoguardrails/actions/llm/utils.py, tests/integrations/langchain/test_actions_llm_utils.py
Adds pattern matching for "NoneType client" AttributeErrors, raising LLMCallException with explicit hints about missing credentials (e.g., api_key, azure_endpoint) instead of generic "Error invoking LLM" messages. Tests verify the hint is included and the original error is preserved as inner_exception.
Model initialization validation
nemoguardrails/integrations/langchain/langchain_initializer.py, tests/integrations/langchain/llm/models/test_langchain_initializer.py
Adds _verify_model_has_usable_client() that runs after model wrapper construction to detect when client and async_client are both None. When detected, raises ModelInitializationError with a list of missing credential kwargs (e.g., api_key, azure_deployment). Tests cover pass cases (client present, async_client present, no client attribute) and failure cases (None client with identifiable missing kwargs, None client without identifiable missing kwargs).
Content safety action error messages
nemoguardrails/library/content_safety/actions.py, tests/test_content_safety_actions.py
Updates content_safety_check_input and content_safety_check_output to include available model names and configuration hints (mentioning content_safety, api_key, azure_endpoint) when the requested model is not found. Test expectations updated for the new message format, and new regression tests verify that initialization failures surface configuration-focused errors rather than opaque NoneType symptoms.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the core fix: clearer error messaging when an Azure content-safety model initialization fails due to missing credentials.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #1338: it adds initialization-time validation to detect missing client, enriches error messages with configuration hints, and includes regression tests for the None-client AttributeError case.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing the None-client initialization failure: LangChain initializer validation, content_safety action error messages, llm_call exception detection, and corresponding tests—all directly supporting the PR objective.
Test Results For Major Changes ✅ Passed PR documents test coverage: new test functions and their scenarios are described. Changes are error-handling improvements with no performance/numeric impact requiring detailed results.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown
Contributor

@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.

Actionable comments posted: 1

🤖 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 `@nemoguardrails/integrations/langchain/langchain_initializer.py`:
- Around line 255-257: Update the docstring near the client/async_client
credential check in langchain_initializer.py to reflect the actual trigger: the
code raises when both client and async_client are None regardless of whether
credential kwargs were omitted or falsy; mention the check is not limited to
wrappers that expose a missing `client` attribute but instead ensures at least
one of `client` or `async_client` is provided. Reference the `client` and
`async_client` variables and the surrounding conditional that raises the error
so the docstring matches current behavior.
🪄 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: Enterprise

Run ID: 48e83aab-00e5-49e6-92dc-c3b8ac1d6907

📥 Commits

Reviewing files that changed from the base of the PR and between e3715f9 and 3b909e2.

📒 Files selected for processing (6)
  • nemoguardrails/actions/llm/utils.py
  • nemoguardrails/integrations/langchain/langchain_initializer.py
  • nemoguardrails/library/content_safety/actions.py
  • tests/integrations/langchain/llm/models/test_langchain_initializer.py
  • tests/integrations/langchain/test_actions_llm_utils.py
  • tests/test_content_safety_actions.py

Comment on lines +255 to +257
This check is intentionally narrow: it only fires when ``client`` is explicitly ``None``
while the corresponding kwarg was either omitted or set to a falsy value, so wrappers
that legitimately do not expose a ``client`` attribute (e.g. providers that lazily
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Docstring does not match the actual trigger condition.

Line 255-257 says this check only fires when missing/falsy credential kwargs are identified, but the implementation raises whenever both client and async_client are None (even with no missing-kwargs evidence). Please align the docstring with current behavior.

✏️ Suggested docstring adjustment
-    This check is intentionally narrow: it only fires when ``client`` is explicitly ``None``
-    while the corresponding kwarg was either omitted or set to a falsy value, so wrappers
-    that legitimately do not expose a ``client`` attribute (e.g. providers that lazily
-    construct their transport) are not affected.
+    This check fires when a wrapper exposes ``client`` but both ``client`` and
+    ``async_client`` are ``None``. Wrappers that do not expose a ``client`` attribute
+    (e.g. providers that lazily construct transport and hide client internals) are not affected.
🤖 Prompt for 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.

In `@nemoguardrails/integrations/langchain/langchain_initializer.py` around lines
255 - 257, Update the docstring near the client/async_client credential check in
langchain_initializer.py to reflect the actual trigger: the code raises when
both client and async_client are None regardless of whether credential kwargs
were omitted or falsy; mention the check is not limited to wrappers that expose
a missing `client` attribute but instead ensures at least one of `client` or
`async_client` is provided. Reference the `client` and `async_client` variables
and the surrounding conditional that raises the error so the docstring matches
current behavior.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 23, 2026

Greptile Summary

This PR improves the developer experience when an Azure (or other OpenAI-compatible) model fails to initialize because a required credential is missing or empty, replacing the cryptic 'NoneType' object has no attribute 'create' traceback with configuration-shaped messages at three defensive layers.

  • _verify_model_has_usable_client is called immediately after init_chat_model returns; if the wrapper carries a client slot that is None it raises ModelInitializationError naming the model, provider, and any falsy credential kwargs.
  • _raise_llm_call_exception recognises the NoneType-attribute-error string pattern and prepends a credential hint to the LLMCallException, acting as a fallback for any broken model that slips through the init-time check.
  • The content_safety actions now report which models actually loaded and surface common credential causes when the requested model is absent.

Confidence Score: 3/5

The init-time check in langchain_initializer.py can be bypassed when langchain_community is installed with Azure support, allowing a broken model to be returned silently and the original NoneType error to resurface.

The exception raised by _verify_model_has_usable_client is caught by the broad except Exception handler in init_langchain_model's retry loop. For users with provider_name="azure_openai" and langchain-community installed, the community initializer runs next and can return a client=None model without the same check, discarding the new helpful error entirely. The fallback in _raise_llm_call_exception still fires, but the init-time layer — the primary fix — does not hold in this configuration.

nemoguardrails/integrations/langchain/langchain_initializer.py — the interaction between _verify_model_has_usable_client and the surrounding retry loop in init_langchain_model

Important Files Changed

Filename Overview
nemoguardrails/integrations/langchain/langchain_initializer.py Adds post-init client check _verify_model_has_usable_client; the exception it raises is caught by the broad except Exception loop in init_langchain_model, allowing a community Azure provider to return a broken model undetected
nemoguardrails/actions/llm/utils.py Adds _is_none_client_attribute_error pattern match and enriches LLMCallException with a credential hint; logic is correct and provides a reliable fallback layer
nemoguardrails/library/content_safety/actions.py Improves model-not-found error messages with available model list and credential hint; both input and output check paths updated symmetrically
tests/integrations/langchain/llm/models/test_langchain_initializer.py Adds five unit tests for _verify_model_has_usable_client; covers happy paths and the regression case; no integration-level test of the swallowed-exception scenario
tests/integrations/langchain/test_actions_llm_utils.py New regression test for NoneType attribute error hint in LLMCallException; assertions are correct and the test exercises the fallback detection path
tests/test_content_safety_actions.py Two new tests verify that the updated model-not-found error message contains credential hints; assertions are correct for the new template strings

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[init_langchain_model] --> B[_init_chat_completion_model]
    B --> C[init_chat_model returns model]
    C --> D{_verify_model_has_usable_client}
    D -->|client != None| E[return model ✓]
    D -->|client == None| F[raise ModelInitializationError]
    F --> G[except Exception in init_langchain_model
last_exception = error]
    G --> H[_init_community_chat_models]
    H --> I{provider in _chat_providers?}
    I -->|No - returns None| J[All initializers exhausted
raise ModelInitializationError
with last_exception message]
    I -->|Yes e.g. azure_openai| K[provider_cls with empty creds]
    K --> L{client == None?}
    L -->|community model also broken| M[return broken model ⚠️
ModelInitializationError silently discarded]
    M --> N[llm_call → AttributeError]
    N --> O{_is_none_client_attribute_error?}
    O -->|Yes| P[LLMCallException with credential hint ✓]
    O -->|No| Q[bare LLMCallException]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
nemoguardrails/integrations/langchain/langchain_initializer.py:236
**`ModelInitializationError` swallowed by the outer retry loop**

`_verify_model_has_usable_client` raises `ModelInitializationError`, but `init_langchain_model` wraps every `try_initialization_method` call in `except Exception as e: last_exception = e`, so the exception is silently stored and the loop advances to `_init_community_chat_models`. That initializer looks up providers in `_chat_providers`, which is populated from `langchain_community.chat_models._module_lookup`. `AzureChatOpenAI` is registered under the key `azure_openai` (last segment of `langchain_community.chat_models.azure_openai`), so for `provider_name="azure_openai"`, the community initializer finds the class, instantiates it with the same empty credentials, and — because there is no equivalent `_verify_model_has_usable_client` call on that path — returns a broken model with `client = None`. `init_langchain_model` returns that model, the helpful `ModelInitializationError` is discarded, and the original `NoneType` attribute error resurfaces at first rail invocation.

### Issue 2 of 2
nemoguardrails/integrations/langchain/langchain_initializer.py:260-266
**`async_client` check inadvertently fires for models with no `async_client` attribute**

When `model.client` is `None` and the model has no `async_client` attribute at all (i.e. the provider only exposes a sync client path), the ternary `getattr(model, "async_client", None) if hasattr(model, "async_client") else None` returns `None`, and `client is not None or async_client is not None` evaluates to `False`, so `ModelInitializationError` is raised. Any LangChain integration that legitimately holds `client = None` during construction before finalising it in a `model_post_init` or validator hook, and that does not expose an `async_client` attribute, would be incorrectly rejected.

Reviews (1): Last reviewed commit: "fix(content_safety): clear error when Az..." | Re-trigger Greptile

except ValueError:
raise

_verify_model_has_usable_client(model, model_name, provider_name, kwargs)
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.

P1 ModelInitializationError swallowed by the outer retry loop

_verify_model_has_usable_client raises ModelInitializationError, but init_langchain_model wraps every try_initialization_method call in except Exception as e: last_exception = e, so the exception is silently stored and the loop advances to _init_community_chat_models. That initializer looks up providers in _chat_providers, which is populated from langchain_community.chat_models._module_lookup. AzureChatOpenAI is registered under the key azure_openai (last segment of langchain_community.chat_models.azure_openai), so for provider_name="azure_openai", the community initializer finds the class, instantiates it with the same empty credentials, and — because there is no equivalent _verify_model_has_usable_client call on that path — returns a broken model with client = None. init_langchain_model returns that model, the helpful ModelInitializationError is discarded, and the original NoneType attribute error resurfaces at first rail invocation.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/integrations/langchain/langchain_initializer.py
Line: 236

Comment:
**`ModelInitializationError` swallowed by the outer retry loop**

`_verify_model_has_usable_client` raises `ModelInitializationError`, but `init_langchain_model` wraps every `try_initialization_method` call in `except Exception as e: last_exception = e`, so the exception is silently stored and the loop advances to `_init_community_chat_models`. That initializer looks up providers in `_chat_providers`, which is populated from `langchain_community.chat_models._module_lookup`. `AzureChatOpenAI` is registered under the key `azure_openai` (last segment of `langchain_community.chat_models.azure_openai`), so for `provider_name="azure_openai"`, the community initializer finds the class, instantiates it with the same empty credentials, and — because there is no equivalent `_verify_model_has_usable_client` call on that path — returns a broken model with `client = None`. `init_langchain_model` returns that model, the helpful `ModelInitializationError` is discarded, and the original `NoneType` attribute error resurfaces at first rail invocation.

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

Comment on lines +260 to +266
if not hasattr(model, "client"):
return

client = getattr(model, "client", None)
async_client = getattr(model, "async_client", None) if hasattr(model, "async_client") else None
if client is not None or async_client is not None:
return
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.

P2 async_client check inadvertently fires for models with no async_client attribute

When model.client is None and the model has no async_client attribute at all (i.e. the provider only exposes a sync client path), the ternary getattr(model, "async_client", None) if hasattr(model, "async_client") else None returns None, and client is not None or async_client is not None evaluates to False, so ModelInitializationError is raised. Any LangChain integration that legitimately holds client = None during construction before finalising it in a model_post_init or validator hook, and that does not expose an async_client attribute, would be incorrectly rejected.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/integrations/langchain/langchain_initializer.py
Line: 260-266

Comment:
**`async_client` check inadvertently fires for models with no `async_client` attribute**

When `model.client` is `None` and the model has no `async_client` attribute at all (i.e. the provider only exposes a sync client path), the ternary `getattr(model, "async_client", None) if hasattr(model, "async_client") else None` returns `None`, and `client is not None or async_client is not None` evaluates to `False`, so `ModelInitializationError` is raised. Any LangChain integration that legitimately holds `client = None` during construction before finalising it in a `model_post_init` or validator hook, and that does not expose an `async_client` attribute, would be incorrectly rejected.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
nemoguardrails/actions/llm/utils.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug:nemoguardrails.actions.llm.utils.LLMCallException: LLM Call Exception: 'NoneType' object has no attribute 'create'

1 participant