Skip to content

fix(streaming): handle parameters.stream=true on model init#1926

Open
adityasingh2400 wants to merge 1 commit into
NVIDIA-NeMo:developfrom
adityasingh2400:adityasingh2400/streaming-asyncstream-guard
Open

fix(streaming): handle parameters.stream=true on model init#1926
adityasingh2400 wants to merge 1 commit into
NVIDIA-NeMo:developfrom
adityasingh2400:adityasingh2400/streaming-asyncstream-guard

Conversation

@adityasingh2400
Copy link
Copy Markdown

@adityasingh2400 adityasingh2400 commented May 23, 2026

Closes #1325.

When users set parameters.stream: true directly on a model in config.yml, providers like the OpenAI client forward the flag verbatim to the HTTP layer, which returns an AsyncStream object on every call. The non-streaming completion path then tries to call .model_dump() on that stream and the server crashes with 'AsyncStream' object has no attribute 'model_dump'.

The fix strips the stream parameter inside LLMRails._prepare_model_kwargs before init and logs a warning that explains the problem and points the user at the correct opt-in (the rails-level streaming flag, or requesting streaming per-call via the API). The strip is narrow: stream_options, which is a legitimate provider knob used to opt into usage accounting on OpenAI streaming responses, is left alone, and an explicit stream: false is silently dropped without a warning because it is already a no-op. The helper now also defensively copies model_config.parameters so the strip cannot mutate the user's RailsConfig in place, which closes a small preexisting hazard for the api_key injection path.

Added three regression tests in tests/test_llmrails.py: the original misconfiguration from the issue is now handled cleanly with a warning, stream_options is preserved, and stream: false is dropped silently. Full tests/test_llmrails.py, tests/_compat, and tests/llm suites continue to pass.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed streaming configuration handling. Applications can no longer pass stream directly within model parameters; streaming must be enabled via the rails streaming flag or API request instead. A warning will now appear if attempted.

Review Change Stack

Closes NVIDIA-NeMo#1325.

When users set parameters.stream: true directly on a model in
config.yml, providers like OpenAI forward the flag to the HTTP
client, which returns an AsyncStream on every call. The
non-streaming completion path then crashes with
'AsyncStream' object has no attribute 'model_dump'.

The _prepare_model_kwargs helper now strips the stream flag at
init time and logs a clear warning pointing users at the right
opt-in (streaming flag on the rails config, or per-call via the
API). stream_options and an explicit stream: false are left
untouched. The kwargs dict is also defensively copied so the
strip does not mutate the user's RailsConfig in place.

Added regression tests covering the misconfiguration, the
stream_options pass-through, and the silent drop of stream:
false.

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

coderabbitai Bot commented May 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 055064ae-3e21-464e-87b7-88524eda1fdd

📥 Commits

Reviewing files that changed from the base of the PR and between e3715f9 and 1d2c3fa.

📒 Files selected for processing (2)
  • nemoguardrails/rails/llm/llmrails.py
  • tests/test_llmrails.py

📝 Walkthrough

Walkthrough

The PR addresses a bug where users configuring stream: true in model parameters caused failures downstream. The implementation adds a defensive copy mechanism and validates against the stream flag during kwargs preparation, logging a warning that streaming must be controlled via the rails API flag. Three regression tests validate the stripping behavior, parameter immutability, and narrow scope of the guard.

Changes

Stream Parameter Guard

Layer / File(s) Summary
Stream parameter guard implementation
nemoguardrails/rails/llm/llmrails.py
_prepare_model_kwargs copies model parameters defensively and introduces a guard that detects stream in parameters, removes it from the returned kwargs, and logs a warning directing users to enable streaming via the rails streaming flag or API request instead of baking it into provider kwargs.
Stream parameter handling regression tests
tests/test_llmrails.py
Three tests verify that stream: True is stripped with a warning and original model parameters are not mutated, that provider-specific stream_options are preserved, and that stream: False is dropped silently without warnings.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 clearly and specifically describes the main change: handling the stream=true parameter on model initialization to prevent crashes.
Linked Issues check ✅ Passed The PR fully addresses issue #1325 by stripping the stream parameter, logging a warning, preserving stream_options, and silently dropping stream:false as expected.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the streaming parameter issue: defensive copying of model_config.parameters, stream parameter stripping logic, warning logging, and regression tests.
Test Results For Major Changes ✅ Passed PR is a minor bug fix (issue #1325) with documented test results: three regression tests were added covering the stream parameter guard, stream_options preservation, and stream: false handling.

✏️ 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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 23, 2026

Greptile Summary

This PR fixes a crash that occurred when users set stream: true directly in a model's parameters block in config.yml. Providers like OpenAI forward the flag verbatim, returning an AsyncStream object that the non-streaming completion path then fails on with 'AsyncStream' object has no attribute 'model_dump'.

  • _prepare_model_kwargs now pops the stream key before passing parameters to the provider, logging a warning when the value is truthy and silently dropping it when falsy. stream_options is explicitly preserved.
  • The method also switches from a direct reference (model_config.parameters or {}) to a defensive shallow copy (dict(model_config.parameters)), closing a preexisting hazard where the api_key injection path could mutate the original RailsConfig object.
  • Three regression tests cover the truthy-stream warning, stream_options passthrough, and the silent falsy-stream drop.

Confidence Score: 4/5

The change is narrow, well-tested, and addresses a real crash; the only loose edges are the hardcoded warning string and the silent drop of stream: false without any observability.

The fix correctly handles the crash scenario and is backed by three targeted regression tests. The defensive copy is a genuine improvement over the old reference-aliasing. The warning message hardcodes stream: true even when the actual value may be 1 or another truthy type, and falsy stream values are silently removed with no debug log, making the behaviour harder to observe during troubleshooting.

Only nemoguardrails/rails/llm/llmrails.py warrants a second look — specifically the warning message fidelity and the lack of any log line for the falsy-stream path.

Important Files Changed

Filename Overview
nemoguardrails/rails/llm/llmrails.py Adds stream key stripping in _prepare_model_kwargs with a warning for truthy values; also switches to a defensive shallow copy of model_config.parameters to prevent in-place mutation. Warning message hardcodes stream: true even when the actual value may differ, and falsy stream values are silently dropped with no log.
tests/test_llmrails.py Adds three focused regression tests covering stream: true (warns + strips), stream_options (preserved), and stream: false (silently dropped). Tests verify no mutation of the original parameters dict and correct warning emission via caplog.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["_prepare_model_kwargs(model_config)"] --> B["Shallow-copy model_config.parameters\ndict(...) if parameters else {}"]
    B --> C{"'stream' key\npresent?"}
    C -- No --> F
    C -- Yes --> D{"value\ntruthy?"}
    D -- "True / 1 / ..." --> E["pop 'stream'\nlog.warning()"]
    D -- "False / 0 / None" --> G["pop 'stream'\n(silent, no warning)"]
    E --> F{"api_key_env_var\nset?"}
    G --> F
    F -- Yes --> H["Resolve env var\ninject kwargs['api_key']"]
    F -- No --> I["return kwargs"]
    H --> I
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/rails/llm/llmrails.py:418-427
**Silent drop of `stream: false` may confuse users**

`kwargs.pop("stream", None)` always removes the `stream` key regardless of its value, but the warning branch fires only when the value is truthy. This means a user who explicitly writes `stream: false` in their config will see the key disappear from the effective kwargs with no log at any level. If they ever try to debug why the parameter is missing, there is nothing in the logs to explain it. A `logging.debug(...)` line for the falsy path would make the behaviour fully observable without adding noise to normal operation.

### Issue 2 of 2
nemoguardrails/rails/llm/llmrails.py:418-427
The warning message hardcodes the literal string `` `stream: true` `` but the condition fires for any truthy value (e.g. `1`, a non-empty string). Embedding the actual value makes the message more actionable when a user sets a non-boolean truthy value.

```suggestion
        stream_val = kwargs.pop("stream", None)
        if stream_val:
            log.warning(
                "Ignoring `stream: %r` set in `parameters` for model %r (engine %r). "
                "Setting `stream` directly on a model causes the provider to return an "
                "AsyncStream object on every call, which breaks the non-streaming path. "
                "Request streaming via the API or set the `streaming` flag on the rails "
                "config instead.",
                stream_val,
                getattr(model_config, "model", None),
                getattr(model_config, "engine", None),
            )
```

Reviews (1): Last reviewed commit: "fix(streaming): guard against parameters..." | Re-trigger Greptile

Comment on lines +418 to +427
if kwargs.pop("stream", None):
log.warning(
"Ignoring `stream: true` set in `parameters` for model %r (engine %r). "
"Setting `stream` directly on a model causes the provider to return an "
"AsyncStream object on every call, which breaks the non-streaming path. "
"Request streaming via the API or set the `streaming` flag on the rails "
"config instead.",
getattr(model_config, "model", None),
getattr(model_config, "engine", None),
)
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 Silent drop of stream: false may confuse users

kwargs.pop("stream", None) always removes the stream key regardless of its value, but the warning branch fires only when the value is truthy. This means a user who explicitly writes stream: false in their config will see the key disappear from the effective kwargs with no log at any level. If they ever try to debug why the parameter is missing, there is nothing in the logs to explain it. A logging.debug(...) line for the falsy path would make the behaviour fully observable without adding noise to normal operation.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/rails/llm/llmrails.py
Line: 418-427

Comment:
**Silent drop of `stream: false` may confuse users**

`kwargs.pop("stream", None)` always removes the `stream` key regardless of its value, but the warning branch fires only when the value is truthy. This means a user who explicitly writes `stream: false` in their config will see the key disappear from the effective kwargs with no log at any level. If they ever try to debug why the parameter is missing, there is nothing in the logs to explain it. A `logging.debug(...)` line for the falsy path would make the behaviour fully observable without adding noise to normal operation.

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

Comment on lines +418 to +427
if kwargs.pop("stream", None):
log.warning(
"Ignoring `stream: true` set in `parameters` for model %r (engine %r). "
"Setting `stream` directly on a model causes the provider to return an "
"AsyncStream object on every call, which breaks the non-streaming path. "
"Request streaming via the API or set the `streaming` flag on the rails "
"config instead.",
getattr(model_config, "model", None),
getattr(model_config, "engine", None),
)
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 The warning message hardcodes the literal string `stream: true` but the condition fires for any truthy value (e.g. 1, a non-empty string). Embedding the actual value makes the message more actionable when a user sets a non-boolean truthy value.

Suggested change
if kwargs.pop("stream", None):
log.warning(
"Ignoring `stream: true` set in `parameters` for model %r (engine %r). "
"Setting `stream` directly on a model causes the provider to return an "
"AsyncStream object on every call, which breaks the non-streaming path. "
"Request streaming via the API or set the `streaming` flag on the rails "
"config instead.",
getattr(model_config, "model", None),
getattr(model_config, "engine", None),
)
stream_val = kwargs.pop("stream", None)
if stream_val:
log.warning(
"Ignoring `stream: %r` set in `parameters` for model %r (engine %r). "
"Setting `stream` directly on a model causes the provider to return an "
"AsyncStream object on every call, which breaks the non-streaming path. "
"Request streaming via the API or set the `streaming` flag on the rails "
"config instead.",
stream_val,
getattr(model_config, "model", None),
getattr(model_config, "engine", None),
)
Prompt To Fix With AI
This is a comment left during a code review.
Path: nemoguardrails/rails/llm/llmrails.py
Line: 418-427

Comment:
The warning message hardcodes the literal string `` `stream: true` `` but the condition fires for any truthy value (e.g. `1`, a non-empty string). Embedding the actual value makes the message more actionable when a user sets a non-boolean truthy value.

```suggestion
        stream_val = kwargs.pop("stream", None)
        if stream_val:
            log.warning(
                "Ignoring `stream: %r` set in `parameters` for model %r (engine %r). "
                "Setting `stream` directly on a model causes the provider to return an "
                "AsyncStream object on every call, which breaks the non-streaming path. "
                "Request streaming via the API or set the `streaming` flag on the rails "
                "config instead.",
                stream_val,
                getattr(model_config, "model", None),
                getattr(model_config, "engine", None),
            )
```

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

✅ All modified and coverable lines are covered by tests.

📢 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: Guardrail server failing when streaming is enabled

1 participant