Skip to content

Remove crashing extra_params.append in NeMoGuardrailsServer.__init__#1947

Open
chuenchen309 wants to merge 2 commits into
NVIDIA:mainfrom
chuenchen309:fix/guardrails-extra-params-crash
Open

Remove crashing extra_params.append in NeMoGuardrailsServer.__init__#1947
chuenchen309 wants to merge 2 commits into
NVIDIA:mainfrom
chuenchen309:fix/guardrails-extra-params-crash

Conversation

@chuenchen309

Copy link
Copy Markdown

What

NeMoGuardrailsServer.__init__ runs:

if self.extra_params and not self.extra_params.get("extra_body"):
    self.extra_params.append("extra_body")

extra_params is a dict (inherited from OpenAICompatible, default {}, consumed via .items()), so .append raises AttributeError: 'dict' object has no attribute 'append'. Any user who sets a non-empty extra_params without an extra_body key crashes generator construction:

# generators.guardrails.NeMoGuardrailsServer.extra_params = {"foo": 1}
NeMoGuardrailsServer(config_root=...)  # AttributeError during __init__

Why remove rather than fix the .append

  • The block only ever crashes (condition true) or is skipped (condition false) — it has never completed successfully, so removing it changes no working behaviour.
  • Guardrails config is actually delivered via the self.extra_body attribute set just below (and passed to the client by the base class), not via an extra_params["extra_body"] key.
  • A naive fix of self.extra_params["extra_body"] = {} would be worse: OpenAICompatible._call_model spreads extra_params into the request after applying self.extra_body, so it would overwrite the real guardrails config with an empty dict.

Test

Added test_nonempty_extra_params_does_not_crash_init: constructing NeMoGuardrailsServer with extra_params={"foo": 1} must not raise and must leave extra_params intact. Fails before the change (AttributeError), passes after. The existing rail-selection tests still pass, confirming the block was vestigial. black --check clean.


Disclosure: this contribution was prepared with the assistance of an AI agent (Claude Code). The analysis, fix, and test were reviewed by me before submission.

__init__ ran:

    if self.extra_params and not self.extra_params.get("extra_body"):
        self.extra_params.append("extra_body")

but extra_params is a dict (OpenAICompatible default {}, consumed via
.items()), so .append raises AttributeError. Any user who sets a non-empty
extra_params without an 'extra_body' key crashes generator construction.

The block is also vestigial: guardrails config is delivered through the
self.extra_body attribute (set just below and passed to the client by the
base class). And a naive 'fix' of setting extra_params["extra_body"] = {}
would be worse -- OpenAICompatible._call_model spreads extra_params into the
request after applying self.extra_body, so it would overwrite the real
guardrails config with an empty dict. Since the block only ever crashes or
is skipped (it never completed successfully), removing it changes no working
behaviour. Existing rail-selection tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andrew Chen <48723787+chuenchen309@users.noreply.github.com>

@jmartin-tech jmartin-tech left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not the correct way to address this issue, please see detailed explaination.

Comment on lines -75 to -76
if self.extra_params and not self.extra_params.get("extra_body"):
self.extra_params.append("extra_body")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Great catch, there is definitely a logic bug here. The approach taken though does not look to align with the intent as the original guard logic was flawed and does not have a clear comment on what it was meant to change after the call to super.__init__() completed.

The fix to this should not be to remove support for an existing extra_body found in the extra_params dict, a fix should properly handle when one does exist. The changes here actually would result in the user provided extra_body overriding what this generator is attempting to support as self.extra_body would shadowed by self.extra_params["extra_body"] in the current OpenAICompatible._call_model().

This suggests the the guard clause is checking the wrong condition and should be improved so the action taken will preform the correct addition. This guard exists augment the existing expectations for extra_params in OpenAICompatible to pass thru any keys in the extra_params provided via config as parameters to the OpenAI client method used for the inference request by merging the guardrails specific extra_body values.

        if self.extra_params and "extra_body" in self.extra_params:
            user_extra_body = self.extra_params.pop("extra_body", None)
            if user_extra_body:
                self.extra_body = user_extra_body

This would align with the rest of the initialization here by setting self.extra_body to the configured value and allowing the guardrails specific injections to be added to a configuration provided extra_body.

Note the suggestion above is still somewhat naive and should probably be extended with type checking to ensure self.extra_params["extra_body"] is a dict to avoid a type mis-match and raise an early exception about invalid configuration values.

Consider exploring adding more unit tests that evaluate how self.extra_body and self.extra_params will behave in the various combinations. Also consider looking at what happens if you were to mock the call to the underlying OpenAI client object in _call_model to see how these values end up mapping into that methods arguments.

Addresses review feedback from @jmartin-tech: the prior commit removed
the crashing `self.extra_params.append("extra_body")` guard outright,
but that left a separate, pre-existing bug in place -- a user-provided
`extra_body` arriving nested inside `extra_params` was never merged
into `self.extra_body`. Since `OpenAICompatible._call_model()` applies
`self.extra_params` *after* the instance's own attributes, a lingering
`extra_params["extra_body"]` would silently overwrite the guardrails
config injected into `self.extra_body`, with the user's own extra_body
also never actually reaching the request in combination with it.

Pop `extra_body` out of `extra_params` (if present) and fold it into
`self.extra_body` before the existing guardrails-merge logic runs, so
the two sources combine instead of one shadowing the other. Also raise
a clear ValueError if the popped value isn't a dict, per review
suggestion, rather than failing confusingly later.

Added two tests: one confirms a user extra_body and the guardrails
config both end up in the actual outgoing request body (verified via
the existing respx-mocked OpenAI client, per review suggestion to
check how these map into the client call); the other confirms a
non-dict extra_body raises ValueError at construction time. Confirmed
both fail against the pre-fix code and pass after. Ran the full
generators/test_guardrails.py + test_openai.py suites (no regressions)
and black --check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andrew Chen <48723787+chuenchen309@users.noreply.github.com>
@chuenchen309 chuenchen309 force-pushed the fix/guardrails-extra-params-crash branch from 2271cc2 to 814f5cc Compare July 14, 2026 00:54
@chuenchen309

Copy link
Copy Markdown
Author

Thanks for the detailed review, @jmartin-tech — you're right, removing the guard outright just traded the crash for a silent shadowing bug. Pushed a fix that pops extra_body out of extra_params and merges it into self.extra_body (using your suggested approach, plus the type-check you mentioned), so a user-configured extra_body and the guardrails config now both make it into the request instead of one clobbering the other.

Added a test that verifies this end-to-end through the mocked OpenAI client (checking the actual outgoing request body has both the user's custom key and the guardrails config merged), plus a test for the ValueError on a non-dict extra_body. Both fail against the previous commit and pass now; existing tests still green.

@jmartin-tech jmartin-tech self-assigned this Jul 14, 2026
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.

2 participants