Skip to content

feat: stream NAT OpenAI chunks natively - #202

Closed
AjayThorve wants to merge 3 commits into
NVIDIA:mainfrom
AjayThorve:feat/nat-openai-streaming
Closed

feat: stream NAT OpenAI chunks natively#202
AjayThorve wants to merge 3 commits into
NVIDIA:mainfrom
AjayThorve:feat/nat-openai-streaming

Conversation

@AjayThorve

@AjayThorve AjayThorve commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Overview

Important

Depends on #200 and #201. This draft targets main because both dependency branches live only on the contributor fork and cannot be selected as base branches in NVIDIA/NeMo-Fabric. The current GitHub diff temporarily includes both prerequisites. Review the NAT integration as git diff 02b2f860..99174abb; 02b2f860 is the corrected #200 commit replayed on top of #201. After both prerequisites merge, this branch will be rebased onto upstream/main and the PR will reduce to its intended four-file delta.

Implements the NAT adapter slice of FABRIC-162 by forwarding NVIDIA NeMo Agent Toolkit ChatResponseChunk values through Fabric's adapter-native OpenAI streaming contract. FABRIC-192 remains a duplicate of FABRIC-162 and is not treated as a separate contract.

Breaking changes: none.

Details

  • Advertises capabilities.streaming: true only with a complete chunk and terminal-result path.
  • Determines support from SessionManager.get_workflow_streaming_output_schema() is ChatResponseChunk; it does not infer support from a registry reference or from shared versus per-user lifecycle.
  • Uses SessionManager.is_workflow_per_user only to validate and forward identity. A future per-user NAT registration that declares ChatResponseChunk can stream without an adapter workflow-name change.
  • Opens exactly one NAT session and run, calls runner.result_stream(to_type=ChatResponseChunk) exactly once, and never calls runner.result() or replays the request.
  • Serializes chunks with NAT's supported to_jsonable_python(..., serialize_unknown=False) boundary and awaits each Fabric emit for backpressure.
  • Preserves chunk ordering and accumulates only emitted choice-0 string delta.content into the normalized terminal response.
  • Supports empty and usage-only streams without synthesizing SSE framing, [DONE], finish chunks, or token usage.
  • Rejects any workflow whose NAT registry metadata does not declare ChatResponseChunk before opening a session. NAT 1.8's current per-user ReAct registration falls into this category because it has no stream function.
  • Preserves primary transport failures and cancellation over secondary generator-cleanup failures, with type-only redacted logging.

Validation

  • just test-python — 804 passed, 22 skipped on the combined branch.
  • Focused NAT adapter suite in the base Fabric environment — 82 passed, 7 skipped.
  • Focused suite against NVIDIA NeMo Agent Toolkit 1.8 — 89 passed; one third-party LangSmith deprecation warning.
  • Real NAT ChatResponseChunk serialization passed the frozen Fabric OpenAI chunk validator, and the installed per-user registration reports no streaming schema.
  • Pre-commit hooks and git diff --check passed.
  • No live model-backed NAT invocation was run.

Where should the reviewer start?

Until #200 and #201 merge, review only external/nat/src/nemo_fabric_adapters/nat/adapter.py, external/nat/fabric-adapter.json, external/nat/README.md, and the streaming cases in tests/adapters/test_external_nat_adapter.py. The dependency-relative range is 02b2f860..99174abb; the key design decision is the output-schema gate in NatRuntime.invoke_openai_stream.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

Summary by CodeRabbit

  • New Features

    • Added native OpenAI Chat Completions streaming to the Python SDK.
    • Added async iteration, terminal results, cleanup, cancellation handling, and capability detection.
    • Added streaming support for shared and per-user NAT ReAct workflows.
    • Added authenticated local transport with validation, ordering, correlation, and error handling.
  • Documentation

    • Added SDK, adapter, schema, and API references distinguishing OpenAI and Relay streaming.
  • Tests

    • Expanded coverage for streaming behavior, failures, cleanup, authentication, and backpressure.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds native OpenAI Chat Completions streaming across Fabric Core, the Python SDK, adapter lifecycle hosts, NAT workflows, schemas, tests, and API documentation. It uses authenticated loopback HTTP with correlated chunk and terminal records.

Changes

Native OpenAI streaming

Layer / File(s) Summary
Core streaming contracts and runtime types
crates/fabric-core/src/error.rs, crates/fabric-core/src/lib.rs, crates/fabric-core/src/runtime.rs, crates/fabric-core/src/schema.rs
Adds authenticated transport types, OpenAI chunk models, correlated NDJSON records, lifecycle dispatch, capability errors, schema registration, local-host dispatch, and redacted payload handling.
Python stream handle and runtime integration
python/src/nemo_fabric/openai_streaming.py, python/src/nemo_fabric/runtime.py, python/src/nemo_fabric/_native.pyi, python/src/nemo_fabric/__init__.py
Adds OpenAIInvokeStream, bounded queues, protocol validation, terminal results, capability checks, stream cleanup, and public exports.
Common host and adapter integration
adapters/common/src/nemo_fabric_adapters/common/lifecycle.py, crates/fabric-python/src/lib.rs, external/nat/src/nemo_fabric_adapters/nat/adapter.py
Adds authenticated chunk transport, payload validation, stream finalization, native binding support, NAT chunk serialization, content aggregation, and session cleanup.
Schemas, guidance, tests, and references
schemas/*, docs/*, skills/*, tests/*, README.md, scripts/*
Adds streaming schemas, contract guidance, generated references, fixtures, navigation updates, and coverage for lifecycle, protocol, authentication, concurrency, and failure behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Runtime
  participant Stream as OpenAIInvokeStream
  participant Host
  participant Adapter
  Client->>Runtime: invoke_openai_stream()
  Runtime->>Stream: create authenticated stream
  Runtime->>Host: dispatch stream invocation
  Host->>Adapter: invoke_openai_stream(payload, emit)
  Adapter->>Stream: emit OpenAI chunks
  Stream->>Client: yield validated chunks
  Adapter->>Host: return terminal result
  Stream->>Client: return RunResult
Loading

Possibly related PRs

  • NVIDIA/NeMo-Fabric#200: Extends the NAT registry workflow changes with native OpenAI streaming support.
  • NVIDIA/NeMo-Fabric#201: Covers the same native OpenAI streaming functionality across the lifecycle, runtime, SDK, adapter, schema, and documentation components.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title uses valid Conventional Commits format, describes the NAT OpenAI streaming change, and is concise without a trailing period.
Description check ✅ Passed The description includes the required overview, reviewer starting point, related issues, validation details, and completed contribution checkboxes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🤖 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 `@adapters/common/src/nemo_fabric_adapters/common/lifecycle.py`:
- Around line 577-612: Update _handle_invoke_openai_stream so adapter transport
or cancellation failures remain the primary exception when writer.finish() also
fails. Preserve and re-raise adapter_error, while logging the cleanup error
through the existing error-reporting mechanism instead of raising it; if only
cleanup fails, continue raising that cleanup error.
- Around line 233-241: Update the HTTP status parsing near status_line so it
does not require a reason phrase: split with a maximum of two splits, then read
the status token by index and convert it to int. Preserve LifecycleError
handling for malformed or undecodable responses while accepting valid lines such
as “HTTP/1.1 200\r\n”.
- Around line 298-370: Confirm the repository Ruff configuration treats PLR0912
and SIM102 as errors, then reduce _validated_openai_chunk’s branch count by
extracting each choice’s validation into a dedicated helper. Within that helper,
collapse the nested checks for function_call, tool_calls, finish_reason, and
logprobs into combined conditions while preserving all existing validation
behavior and error messages.
- Around line 122-136: Consolidate the duplicated writer cleanup in the
lifecycle operation around the `LifecycleError` and generic `Exception` handlers
into a `finally` block, preserving the existing error propagation and wrapping
behavior. Guard cleanup with the existing writer-local check and change
`suppress(ConnectionError)` to `suppress(OSError)` so `wait_closed()` failures
do not replace the intended error. Apply the same suppression widening in
`finish()`.

In `@crates/fabric-core/src/runtime.rs`:
- Around line 829-834: Update validate_adapter_compatibility to validate
streaming support using the resolved adapter descriptor’s capabilities, rather
than only plan.capabilities.streaming. Reject streaming when the requested plan
and adapter descriptor are incompatible, while preserving the existing
UnsupportedRuntimeCapability error context. Add a regression test covering a
stale mismatched RunPlan that attempts streaming with an adapter lacking
streaming support.

In `@docs/reference/api/python-library-reference/nemo_fabric.openai_streaming.md`:
- Around line 20-22: Update the source docstring or documentation generator that
produces the OpenAI streaming API reference so :meth:`result` and :meth:`aclose`
become valid Markdown links, and convert any emitted :class: roles similarly.
Regenerate the API references afterward; do not modify docs/reference/api/
directly.

In
`@docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaichatcompletionchunkobject.mdx`:
- Line 2: Update the reference-title generator to preserve the official “OpenAI”
capitalization, then run “just docs” to regenerate the affected pages. The
generated titles must be “Enum OpenAI Chat Completion Chunk Object” in
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaichatcompletionchunkobject.mdx:2-2,
“Enum OpenAI Stream Host” in
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamhost.mdx:2-2,
“Enum OpenAI Stream Profile” in
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamprofile.mdx:2-2,
“Enum OpenAI Stream Protocol Version” in
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamprotocolversion.mdx:2-2,
and “Enum OpenAI Stream Record” in
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx:2-2;
do not edit these generated pages directly.

In
`@docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunk.mdx`:
- Line 2: Update the generator’s identifier display mapping so the OpenAI prefix
retains official capitalization and renders “OpenAI Chat Completion” rather than
“Open AiChat”; then regenerate the affected pages:
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunk.mdx:2,
struct-openaichatcompletionchunkchoice.mdx:2, and
struct-openaichatcompletionchunkdelta.mdx:2. Do not edit the generated reference
pages directly; each must produce the specified OpenAI Chat Completion Chunk,
Choice, and Delta titles.

In `@docs/sdk/python.mdx`:
- Around line 463-468: Update the stream lifecycle guidance to require awaiting
stream.result() after fully consuming the iterator and before starting another
turn, ensuring terminal RunResult completion. For abandoned streams, require
awaiting stream.aclose() instead; clarify that either operation must complete
before reusing the runtime.

In `@external/nat/README.md`:
- Line 18: Update the prose references in the README to consistently use “NeMo
Fabric” instead of standalone “Fabric,” including the references to supported
intent, portable agent intent, and terminal result. Preserve the configuration
value `fabric.agent.react` unchanged.

In `@schemas/adapter-contract/legacy/openai-stream-invocation.schema.json`:
- Around line 170-178: Introduce a new adapter contract version for
InvokeOpenaiStream so OpenAiStreamSink.protocol_version can negotiate the
lifecycle operation. Update OpenAiStreamProtocolVersion, all related
descriptors, and snapshots to use the new version, while rejecting
fabric.adapter/v1alpha2 before lifecycle dispatch rather than routing by
streaming alone. Add a regression test covering rejection of the previous
contract version.

In `@tests/adapters/test_external_nat_adapter.py`:
- Around line 307-338: Pin the NAT dependency used by
test_installed_nat_chat_response_chunk_serializes_to_openai_mapping to a
compatible 1.8 release, or add an explicit version check before invoking
ChatResponseChunk.from_string so incompatible installations are skipped instead
of failing with a signature error.

In `@tests/python/test_openai_streaming.py`:
- Around line 810-826: Rename the test’s openai_streaming parameter and its
parametrization tuple entry to avoid shadowing the imported openai_streaming
module, and update the _runtime_wrapper argument and assertion in
test_native_and_relay_streaming_capabilities_are_independent to use the new
parameter name.
- Around line 766-781: Initialize connections before entering the try block,
then update the cleanup loop to access connections directly instead of using
locals().get(). Preserve the existing socket-closing and listener-closing
behavior while allowing missing connection data to raise clearly.
- Around line 482-487: Bound the polling loop in the streaming cancellation test
around stream._end_observed with a finite deadline or timeout, and fail with a
clear assertion message if the flag is not observed in time. Preserve the
existing iterator cancellation and asyncio.CancelledError assertions after
successful observation.
🪄 Autofix

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: ASSERTIVE

Plan: Enterprise

Run ID: a43c8fb4-4207-44c2-b4a2-ad7fd15e4b0a

📥 Commits

Reviewing files that changed from the base of the PR and between 4112bea and 40721d7.

📒 Files selected for processing (78)
  • README.md
  • adapters/common/README.md
  • adapters/common/src/nemo_fabric_adapters/common/lifecycle.py
  • crates/fabric-core/src/error.rs
  • crates/fabric-core/src/lib.rs
  • crates/fabric-core/src/runtime.rs
  • crates/fabric-core/src/schema.rs
  • crates/fabric-python/src/lib.rs
  • docs/adapter-contract/conformance.md
  • docs/adapter-contract/execution.md
  • docs/index.yml
  • docs/reference/api/python-library-reference/index.md
  • docs/reference/api/python-library-reference/nemo_fabric.openai_streaming.md
  • docs/reference/api/python-library-reference/nemo_fabric.runtime.md
  • docs/reference/api/python-library-reference/nemo_fabric.streaming.md
  • docs/reference/api/rust-library-reference/nemo-fabric-core/adapter-contract/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/agent-config/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/agent-execution/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/doctor/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/fn-version.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/constant-openai-chat-completions-chunk-profile.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/constant-openai-stream-host.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/constant-openai-stream-protocol-version.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-errorstage.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaichatcompletionchunkobject.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamhost.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamprofile.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamprotocolversion.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-runstatus.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-invoke-openai-stream.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-invoke-runtime.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-prepare-environment.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-run-plan.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-start-runtime.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/fn-stop-runtime.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunk.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunkchoice.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunkdelta.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreaminvocation.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamtransport.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runrequest.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runresult.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimecontext.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimehandle.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-runtimetelemetrycontext.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-telemetryref.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/schema/enum-schemaname.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/schema/index.mdx
  • docs/sdk/python.mdx
  • external/nat/README.md
  • external/nat/fabric-adapter.json
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/_native.pyi
  • python/src/nemo_fabric/openai_streaming.py
  • python/src/nemo_fabric/runtime.py
  • schemas/SCHEMA.md
  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
  • schemas/adapter-contract/legacy/openai-stream-record.schema.json
  • scripts/docs/enhance_python_api_reference.py
  • scripts/generate_api_docs.sh
  • skills/nemo-fabric-build-adapter/SKILL.md
  • skills/nemo-fabric-integrate/SKILL.md
  • skills/nemo-fabric-integrate/references/sdk-api-inventory.md
  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/adapters/test_external_nat_adapter.py
  • tests/docs/test_python_api_docs.py
  • tests/fixtures/hermes-shim-agent/adapters/hermes-shim/fabric-adapter.json
  • tests/fixtures/hermes-shim-agent/adapters/hermes-shim/src/nemo_fabric_test_adapters/hermes_shim/adapter.py
  • tests/python/test_native_sdk.py
  • tests/python/test_openai_streaming.py

Comment thread adapters/common/src/nemo_fabric_adapters/common/lifecycle.py
Comment on lines +233 to +241
status_line = await reader.readline()
try:
_version, raw_status, _reason = status_line.decode("ascii").split(" ", 2)
status = int(raw_status)
except (UnicodeDecodeError, ValueError) as error:
raise LifecycleError(
"lifecycle_stream_transport_failed",
"OpenAI stream listener returned an invalid HTTP response",
) from error

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Parse the HTTP status line without requiring a reason phrase.

split(" ", 2) unpacks into exactly three values. A response line such as HTTP/1.1 200\r\n produces two parts and raises ValueError. The code then reports lifecycle_stream_transport_failed even though the listener returned a valid status. RFC 9112 allows an empty reason phrase. Split with maxsplit=2 and read the status by index instead.

🐛 Proposed fix
     status_line = await reader.readline()
     try:
-        _version, raw_status, _reason = status_line.decode("ascii").split(" ", 2)
-        status = int(raw_status)
+        parts = status_line.decode("ascii").split(" ", 2)
+        if len(parts) < 2:
+            raise ValueError("missing HTTP status code")
+        status = int(parts[1])
     except (UnicodeDecodeError, ValueError) as error:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
status_line = await reader.readline()
try:
_version, raw_status, _reason = status_line.decode("ascii").split(" ", 2)
status = int(raw_status)
except (UnicodeDecodeError, ValueError) as error:
raise LifecycleError(
"lifecycle_stream_transport_failed",
"OpenAI stream listener returned an invalid HTTP response",
) from error
status_line = await reader.readline()
try:
parts = status_line.decode("ascii").split(" ", 2)
if len(parts) < 2:
raise ValueError("missing HTTP status code")
status = int(parts[1])
except (UnicodeDecodeError, ValueError) as error:
raise LifecycleError(
"lifecycle_stream_transport_failed",
"OpenAI stream listener returned an invalid HTTP response",
) from error
🤖 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 `@adapters/common/src/nemo_fabric_adapters/common/lifecycle.py` around lines
233 - 241, Update the HTTP status parsing near status_line so it does not
require a reason phrase: split with a maximum of two splits, then read the
status token by index and convert it to int. Preserve LifecycleError handling
for malformed or undecodable responses while accepting valid lines such as
“HTTP/1.1 200\r\n”.

Comment thread adapters/common/src/nemo_fabric_adapters/common/lifecycle.py
Comment thread crates/fabric-core/src/runtime.rs Outdated
Comment thread docs/reference/api/python-library-reference/nemo_fabric.openai_streaming.md Outdated
Comment thread tests/adapters/test_external_nat_adapter.py
Comment thread tests/python/test_openai_streaming.py Outdated
Comment on lines +482 to +487
iterator = asyncio.create_task(anext(stream))
while not stream._end_observed:
await asyncio.sleep(0)
iterator.cancel()
with pytest.raises(asyncio.CancelledError):
await iterator

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound the wait on _end_observed.

Lines 483-484 spin on stream._end_observed with await asyncio.sleep(0). asyncio.sleep(0) yields without blocking, so this loop saturates the event loop. If a regression prevents _end_observed from becoming true, the test hangs instead of failing. Add a deadline so the test fails with a clear message.

♻️ Proposed fix to add a deadline to the wait loop
     iterator = asyncio.create_task(anext(stream))
-    while not stream._end_observed:
-        await asyncio.sleep(0)
+    deadline = time.monotonic() + 5
+    while not stream._end_observed:
+        assert time.monotonic() < deadline, "stream never observed its end record"
+        await asyncio.sleep(0.001)
     iterator.cancel()
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 483-484: Use asyncio.Event instead of awaiting asyncio.sleep in a while loop

(ASYNC110)

🤖 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 `@tests/python/test_openai_streaming.py` around lines 482 - 487, Bound the
polling loop in the streaming cancellation test around stream._end_observed with
a finite deadline or timeout, and fail with a clear assertion message if the
flag is not observed in time. Preserve the existing iterator cancellation and
asyncio.CancelledError assertions after successful observation.

Source: Linters/SAST tools

Comment thread tests/python/test_openai_streaming.py
Comment thread tests/python/test_openai_streaming.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@external/nat/src/nemo_fabric_adapters/nat/adapter.py`:
- Around line 835-959: Extract the inner stream-draining and response
aggregation logic from invoke_openai_stream into a focused private helper,
including chunk serialization, emit ordering, content collection, and stream
cleanup semantics. Have invoke_openai_stream call the helper while preserving
cancellation and lifecycle error propagation; leave all existing LOGGER.error
calls unchanged.

In `@tests/adapters/test_external_nat_adapter.py`:
- Around line 461-465: Add an explicit boolean parameter to the relevant test
cases indicating whether the workflow is expected to be ReAct, and use that
parameter for both the assertion near result["workflow"]["_type"] and the
fixture setup near the second occurrence. Remove the duplicated
expected_type.rsplit("/", 1)[-1] membership checks so the test does not
reimplement _is_react_agent.
🪄 Autofix

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: ASSERTIVE

Plan: Enterprise

Run ID: 229fe51f-4228-467f-bd5c-e82e6d02c28c

📥 Commits

Reviewing files that changed from the base of the PR and between 40721d7 and e551aa0.

📒 Files selected for processing (4)
  • external/nat/README.md
  • external/nat/fabric-adapter.json
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
📜 Review details
⏰ Context from checks skipped due to timeout. (16)
  • GitHub Check: Preview docs
  • GitHub Check: Test (Python 3.11, linux-amd64)
  • GitHub Check: Test (Python 3.12, linux-arm64)
  • GitHub Check: Test (Python 3.11, macos-arm64)
  • GitHub Check: Test (Python 3.12, macos-arm64)
  • GitHub Check: Test (Python 3.11, linux-arm64)
  • GitHub Check: Test (Python 3.13, linux-amd64)
  • GitHub Check: Test (Python 3.14, macos-arm64)
  • GitHub Check: Test (Python 3.13, macos-arm64)
  • GitHub Check: Test (Python 3.14, linux-amd64)
  • GitHub Check: Test (Python 3.11, windows-amd64)
  • GitHub Check: Test (Python 3.14, windows-amd64)
  • GitHub Check: Test (Python 3.14, linux-arm64)
  • GitHub Check: Test (Python 3.13, windows-amd64)
  • GitHub Check: Test (Python 3.12, linux-amd64)
  • GitHub Check: Pre-commit
🧰 Additional context used
📓 Path-based instructions (21)
**/*.{rs,py,pyi,json,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Determine and update every affected public surface, including the CLI, PyO3 bindings, Python SDK, type stubs, schemas, and adapter contract, so they remain in parity.

Files:

  • external/nat/fabric-adapter.json
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*

📄 CodeRabbit inference engine (.agents/skills/karpathy-guidelines/SKILL.md)

**/*: Before implementing, explicitly state assumptions, surface ambiguity and tradeoffs, present multiple interpretations when relevant, and ask for clarification rather than silently deciding or proceeding when requirements are unclear.
Prefer the minimum code needed to solve the requested problem: avoid speculative features, unnecessary abstractions, unrequested flexibility, and handling of impossible scenarios; simplify overcomplicated solutions.
When editing existing code, make surgical changes only: do not modify unrelated code, comments, formatting, or pre-existing dead code; match the existing style, and remove only unused imports, variables, or functions introduced by your changes.
Define verifiable success criteria for each task, such as writing regression tests for bugs and invalid-input tests for validation, then verify the implementation against those criteria. For multi-step work, state a brief plan with a verification check for each step.

**/*: Always spell NVIDIA in all caps; do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun, because the name begins with an “en” sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company; use trademark symbols with product names only when required by the document type or legal guidance.
Verify official capitalization, spacing, hyphenation, and spelling for NVIDIA and third-party product names; do not rewrite official product names for grammar or title-case rules.
Precede NVIDIA product names with NVIDIA on first mention when natural and accurate, and link the first mention when the destination helps the reader.
On first use, include the company name and full model qualifier when it helps identify the model; preserve official capitalization and punctuation, and use shorter family names only after establishing the full name.
For learning-oriented and developer content, do not force trademark symbols unless explicitly required; for press, ...

Files:

  • external/nat/fabric-adapter.json
  • external/nat/README.md
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{json,jsonschema}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Public contract changes must keep checked-in JSON Schema snapshots synchronized.

Files:

  • external/nat/fabric-adapter.json
**/*.{md,rst}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Update documentation and examples in the same branch as the public API change.

Files:

  • external/nat/README.md
**/*.{md,mdx,rst}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-guide.md)

**/*.{md,mdx,rst}: For NeMo Fabric documentation, verify technical claims against the current repository, public API, or documented command before reviewing style.
Always spell NVIDIA in all caps; do not use Nvidia, nvidia, or NV.
Format commands, code elements, expressions, package names, file names, and paths as inline code.
Use descriptive link text; avoid raw URLs and weak anchors such as here or read more.
Use title case consistently for technical documentation headings.
Introduce code blocks, lists, tables, and images with complete sentences.
Write procedures as imperative, parallel steps; split long procedures into smaller tasks.
Prefer active voice, present tense, short sentences, contractions, and plain English while preserving necessary technical precision.
Use can for possibility and reserve may for permission.
Use after for temporal relationships instead of once, and prefer refer to over see when directing readers to another resource.
Avoid culture-specific idioms, unnecessary Latinisms, jokes, and marketing exaggeration in technical documentation.
Spell out months in body text, avoid ordinal dates, and use clear time zones.
Spell out whole numbers from zero through nine unless they are technical values, parameters, versions, or UI values; use numerals for 10 or greater and commas in thousands.
Do not add trademark symbols to learning-oriented documentation unless the source, platform, or legal guidance explicitly requires them.
Do not replace precise technical terms with simpler words when doing so would lose precision.
Do not flag passive voice when the actor is unknown or the action is the important part.
Do not rewrite API names, package names, command flags, or code literals for style.

**/*.{md,mdx,rst}: Use consistent title case for technical-document headings and table headers; avoid quotation marks, ampersands, and exclamation marks in headings, while preserving official product, event, research, and whitepaper title ...

Files:

  • external/nat/README.md
**/*.{md,rst,txt,adoc}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-language-mechanics.md)

**/*.{md,rst,txt,adoc}: For technical documentation, use professional, active, conversational, engaging, precise, and plain-English prose. Prefer active voice, present tense, short sentences, and scannable paragraphs. Avoid casual or imprecise language, swearing, threats, insults, jokes, puns, culture-specific idioms, marketing exaggeration, and unsupported third-party comparisons.
Use can for possibility and reserve may for permission; use after for temporal order; use refer to for cross-references; prefer short direct sentences and specific verbs; avoid unnecessary please in technical documentation.
Prefer active voice when the actor matters. Passive voice is acceptable when the actor is unknown or irrelevant, when the action or result is the focus, or in programmer documentation.
Use natural contractions in conversational technical prose, but do not force them in formal legal copy, API references, or generated text.
Prefer simpler English over Latinisms: use for example or such as instead of e.g., and so on instead of etc., that is instead of i.e., compared to instead of vs., and by, through, or using instead of via. Use industry-standard terms such as in silico, in vitro, and in vivo when appropriate, and italicize them in running text.
Use that without commas for essential clauses, and which with commas for nonessential clauses.
Format dates and times clearly: spell out months in body text; use forms such as June 12, 2025; avoid numeric or ordinal dates; capitalize days; use 12-hour time when appropriate; include a space before a.m. or p.m.; use ET and PT for needed time zones; avoid 24/7; and prefer from 12:30 to 1:00 p.m. for prose ranges.
Format numbers consistently: spell out zero through nine in body text, use numerals for 10 or greater and for technical values, use commas in thousands, do not begin a sentence with a numeral, spell out ordinals, and use numerals consistently within a category wh...

Files:

  • external/nat/README.md
**/*.{md,mdx,yml,py,rs,sh}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Keep documentation aligned with current NeMo Fabric behavior, repository layout, entry points, commands, package names, APIs, bindings, and support claims.

Files:

  • external/nat/README.md
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{md,mdx}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

**/*.{md,mdx}: Use the full product name NVIDIA NeMo Fabric on first use, typically in the title and H1; use NeMo Fabric thereafter. Use fabric alone only for the CLI tool and surround it with backticks.
Treat incorrect or stale commands, package names, paths, APIs, support claims, procedures, examples, terminology, or public behavior documentation as blocking issues.
Capitalize NVIDIA correctly and format code, commands, paths, and filenames as inline code where needed.
Use title case for technical-documentation headings.
Introduce code blocks, tables, and lists with complete lead-in sentences; ensure examples match current APIs and build commands.
Use descriptive anchor text, avoid raw URLs and generic labels such as here, and use repository-relative .mdx paths for links within docs/.
Prefer active voice, present tense, short sentences, plain English, consistent terminology, and imperative, parallel, scannable procedures.
Use after instead of once when expressing temporal sequence, and use can rather than may when describing possibility rather than permission.
Avoid ambiguous numeric dates and ordinal dates in body text.
For learning-oriented documentation, do not force trademark symbols unless the source document explicitly requires them.
When reporting documentation-review findings, lead with Must fix, Should fix, and Nice to have categories; include file path, line reference, current problem, rationale, and a concrete rewrite or direction.

Files:

  • external/nat/README.md
**/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update relevant SDK, API reference, adapter, example, integration, and support documentation when the corresponding public surface changes.

Files:

  • external/nat/README.md
**/*.{html,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

HTML and Markdown files must use the specified SPDX HTML-comment header.

Files:

  • external/nat/README.md
{*.md,**/*.md,**/*.mdx,**/*.ipynb}

⚙️ CodeRabbit configuration file

{*.md,**/*.md,**/*.mdx,**/*.ipynb}: Enforce the product name in user-facing prose: use "NVIDIA NeMo Fabric" on first use and "NeMo Fabric" thereafter. Flag standalone capitalized "Fabric" when it refers to the product. Do not flag the lowercase fabric CLI command, package/import/crate names, code identifiers, API symbols, configuration keys, file paths, or unrelated generic uses of the word.

Files:

  • external/nat/README.md
**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For native binding changes, run cargo check -p fabric-python --locked.

Use snake_case for functions and variables; use PascalCase for Rust types and Python classes.

Files:

  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Python code or a Python-facing adapter changes, run just test-python.

In Python SDK, adapters, examples, and tests, follow the existing style, use type annotations for public APIs, and keep native binding declarations synchronized with their Rust implementations.

Files:

  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{rs,py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{rs,py,pyi}: If public configuration types change, confirm schema snapshot tests in just test-rust pass and review generated schema diffs.
For schema or public contract changes, run both language suites and review changes under schemas/ and generated API references.

Files:

  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{py,pyi,rs}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

For Python SDK or PyO3 binding changes, use python-tests, run focused pytest tests first, then just test-python; rebuild with just build-python when native code or packaging changes.

Public contract changes must keep native Python binding declarations synchronized with their Rust implementations.

Files:

  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{rs,py,toml}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

When editing version helpers, verify every nemo-fabric-* workspace package through Cargo metadata and reject a static version in python/pyproject.toml.

Files:

  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{toml,rs,py}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Avoid blind repository-wide replacement of version-like strings; distinguish package-version references from examples and unrelated dependency versions.

Files:

  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • tests/adapters/test_external_nat_adapter.py
tests/adapters/**/*.py

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

tests/adapters/**/*.py: If an adapter or integration changes, run its focused tests.
For adapter behavior changes, run focused adapter tests under tests/adapters, then run just test-python.

Files:

  • tests/adapters/test_external_nat_adapter.py
tests/**/*.{rs,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the corresponding Rust crate or the relevant area under tests/.

Files:

  • tests/adapters/test_external_nat_adapter.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run Python tests.
Do not add @pytest.mark.asyncio to tests; async tests are automatically detected by the async runner.
Do not add -> None return type annotations to test functions.
When mocking a class, use unittest.mock.MagicMock or AsyncMock, using the spec argument when necessary, rather than defining a new class.
Prefix mocked class names with mock, not fake.
Prefer pytest fixtures over helper methods.
If a fixture is needed in multiple test files, define it once in conftest.py rather than repeating it.
Define fixtures using @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and a <fixture_name>_fixture function; specify scope only when it is not function.
Prefer pytest.mark.parametrize over separate tests for different input types.
Use @pytest.mark.usefixtures when a fixture is needed but its returned value is unused or it returns no value.
Avoid defensive programming in tests; access expected values directly so missing data raises a clear failure, such as using results["data"] instead of results.get("data").
When adapter installation metadata changes, packaging metadata tests must directly assert that the root project depends unconditionally on the exact-version nemo-fabric-runtime distribution.
Packaging metadata tests must verify that each root harness extra delegates to the matching version of the leaf adapter's harness extra.
Packaging metadata tests must verify that bare leaf dependencies remain adapter-owned and that the root adapter-tests dependency group installs each leaf through its harness extra.
Packaging metadata tests must verify that every leaf provides full; only adapters importing NeMo Relay Python APIs provide relay, while adapters using an external Relay executable have full equal to harness.

Files:

  • tests/adapters/test_external_nat_adapter.py
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/adapters/test_external_nat_adapter.py
🧠 Learnings (1)
📚 Learning: 2026-06-29T22:34:52.407Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 27
File: adapters/codex-cli/fabric-adapter.json:13-15
Timestamp: 2026-06-29T22:34:52.407Z
Learning: In NeMo-Fabric adapter manifest files (e.g., `*/fabric-adapter.json`), keep `config.accepts` limited to the top-level Fabric capability sections that `resolve_capability_plan` consumes (such as `models`, `tools`, `mcp`, `skills`, `telemetry`). Do not add adapter-owned `harness.settings` keys to `config.accepts`; `harness.settings` should remain adapter-owned and be passed through unchanged.

Applied to files:

  • external/nat/fabric-adapter.json
🪛 ast-grep (0.45.1)
tests/adapters/test_external_nat_adapter.py

[info] 1336-1336: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1374-1374: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.16.1)
external/nat/src/nemo_fabric_adapters/nat/adapter.py

[warning] 656-659: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 835-835: Too many return statements (7 > 6)

(PLR0911)


[warning] 835-835: Too many branches (21 > 12)

(PLR0912)


[warning] 835-835: Too many statements (65 > 50)

(PLR0915)


[warning] 854-854: Do not catch blind exception: Exception

(BLE001)


[warning] 855-858: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 883-887: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


[warning] 909-909: Abstract raise to an inner function

(TRY301)


[warning] 926-926: Do not catch blind exception: Exception

(BLE001)


[warning] 927-931: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 940-943: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 948-948: Do not catch blind exception: Exception

(BLE001)


[warning] 949-952: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

tests/adapters/test_external_nat_adapter.py

[warning] 45-45: Missing return type annotation for special method __aiter__

(ANN204)


[warning] 48-48: Missing return type annotation for special method __anext__

(ANN204)


[warning] 1350-1350: Dynamically typed expressions (typing.Any) are disallowed in serialization_failure

(ANN401)

🔇 Additional comments (9)
external/nat/README.md (2)

56-68: Use NeMo Fabric for the product name in prose.

Line 56 reads "separate Fabric runtimes" and line 68 reads "The terminal Fabric result". Both use a standalone capitalized Fabric for the product. The rest of the file uses NeMo Fabric.

📝 Proposed wording
-builder, different users remain isolated, and separate Fabric runtimes own
-separate session managers.
+builder, different users remain isolated, and separate NeMo Fabric runtimes own
+separate session managers.
@@
-serialized OpenAI Chat Completions chunks in order. The terminal Fabric result
+serialized OpenAI Chat Completions chunks in order. The terminal NeMo Fabric result
 contains the concatenated string `delta.content` values for choice index `0`;

As per path instructions: "Enforce the product name in user-facing prose: use "NVIDIA NeMo Fabric" on first use and "NeMo Fabric" thereafter. Flag standalone capitalized "Fabric" when it refers to the product."

Source: Path instructions


59-73: LGTM!

Also applies to: 89-92

tests/adapters/test_external_nat_adapter.py (4)

310-343: Pin or guard the NAT version used by this installed-NAT test.

pytest.importorskip skips when NAT is absent. It does not skip when an incompatible NAT version is installed. ChatResponseChunk.from_string(..., id_=..., model=..., finish_reason=...) then fails with a signature error instead of skipping. Pin the compatible NAT release or add an explicit version guard.


32-62: LGTM!

Also applies to: 157-157, 169-169, 190-190, 203-207, 229-229, 289-292


468-507: LGTM!

Also applies to: 575-620, 668-789


1085-1304: LGTM!

Also applies to: 1306-1423, 1426-1574

external/nat/fabric-adapter.json (1)

24-31: LGTM!

Also applies to: 91-91

external/nat/src/nemo_fabric_adapters/nat/adapter.py (2)

9-11: LGTM!

Also applies to: 34-34, 52-55, 111-113, 214-217


638-670: LGTM!

Also applies to: 721-745, 787-795

Comment on lines +835 to +959
async def invoke_openai_stream(
self,
payload: dict[str, Any],
emit: lifecycle.OpenAIChunkEmitter,
) -> dict[str, Any]:
"""Forward one NAT result stream declared as OpenAI chunks."""

request, failure = self._invocation_request(payload)
if failure is not None:
return failure
assert request is not None
assert self._sessions is not None

try:
from nat.data_models.api_server import ChatResponseChunk

streaming_output_schema = (
self._sessions.get_workflow_streaming_output_schema()
)
except Exception as error:
LOGGER.error(
"NAT workflow streaming schema lookup failed (error_type=%s)",
type(error).__name__,
)
return _failure_output(
"nat_workflow_stream_failed",
"NAT workflow streaming failed; inspect adapter stderr for details",
)

if streaming_output_schema is not ChatResponseChunk:
return _failure_output(
"nat_openai_stream_unsupported_schema",
"NAT native OpenAI streaming requires a ChatResponseChunk output schema",
)

try:
session_kwargs = _session_kwargs(
request,
require_user_id=self._sessions.is_workflow_per_user,
)
except ValueError as error:
return _failure_output("nat_invalid_request", str(error))

response_parts: list[str] = []
try:
from nat.data_models.runtime_enum import RuntimeTypeEnum
from pydantic_core import to_jsonable_python

async with self._sessions.session(**session_kwargs) as session:
async with session.run(
request.get("input", ""),
runtime_type=RuntimeTypeEnum.RUN_OR_SERVE,
) as runner:
stream = runner.result_stream(to_type=ChatResponseChunk)

async def close_stream() -> None:
close = getattr(stream, "aclose", None)
if callable(close):
await close()

try:
async for chunk in stream:
try:
serialized = to_jsonable_python(
chunk,
serialize_unknown=False,
)
except asyncio.CancelledError:
raise
except lifecycle.LifecycleError:
raise
except Exception as error:
raise _NatStreamSerializationError from error
if not isinstance(serialized, dict):
raise _NatStreamSerializationError

await emit(serialized)
for choice in serialized.get("choices", []):
if not isinstance(choice, dict):
continue
if choice.get("index") != 0:
continue
delta = choice.get("delta")
if not isinstance(delta, dict):
continue
content = delta.get("content")
if isinstance(content, str):
response_parts.append(content)
except BaseException:
try:
await close_stream()
except Exception as error:
LOGGER.error(
"NAT workflow stream cleanup failed while preserving "
"the primary failure (error_type=%s)",
type(error).__name__,
)
raise
else:
await close_stream()
except asyncio.CancelledError:
raise
except lifecycle.LifecycleError:
raise
except _NatStreamSerializationError as error:
LOGGER.error(
"NAT workflow returned a non-JSON stream chunk (error_type=%s)",
type(error.__cause__ or error).__name__,
)
return _failure_output(
"nat_stream_chunk_not_json_serializable",
"NAT workflow returned a stream chunk that cannot be represented as JSON",
)
except Exception as error:
LOGGER.error(
"NAT workflow streaming failed (error_type=%s)",
type(error).__name__,
)
return _failure_output(
"nat_workflow_stream_failed",
"NAT workflow streaming failed; inspect adapter stderr for details",
)

return _success_output("".join(response_parts))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Determine whether Ruff enforces PLR0911/PLR0912/PLR0915 and how per-file ignores apply.
set -uo pipefail

echo "== Ruff configuration files =="
fd -H -t f 'ruff.toml|.ruff.toml|pyproject.toml' | while IFS= read -r file; do
  echo "--- $file ---"
  rg -n -A 40 '\[tool\.ruff|^\[lint\]|^\[tool\.ruff\.lint\]' "$file"
done

echo
echo "== Explicit references to the reported rules =="
rg -n 'PLR0911|PLR0912|PLR0915|"PL"|"TRY"|"BLE"|per-file-ignores' --glob '*.toml'

echo
echo "== Lint recipes =="
fd -H -t f 'justfile|Justfile|.justfile' --exec rg -n -A 6 'lint|ruff' {}

Repository: NVIDIA/NeMo-Fabric

Length of output: 947


🏁 Script executed:

set -u

echo "== Ruff-related files and recipes =="
git ls-files | rg '(^|/)(pyproject\.toml|ruff\.toml|\.ruff\.toml|Justfile|justfile|\.justfile)$' |
  while IFS= read -r file; do
    echo "--- $file ---"
    rg -n -A 12 -B 4 'ruff|lint|PLR0911|PLR0912|PLR0915|per-file-ignores' "$file" || true
  done

echo
echo "== Adapter project configuration =="
fd -H -t f 'pyproject.toml|ruff.toml|.ruff.toml' external/nat --exec sh -c '
  for file do
    echo "--- $file ---"
    cat -n "$file"
  done
' sh

Repository: NVIDIA/NeMo-Fabric

Length of output: 1003


🏁 Script executed:

set -u

echo "== All Ruff invocations and lint entry points =="
rg -n -S -g '!*.lock' -g '!*.sum' '(^|[^[:alnum:]_])ruff([^[:alnum:]_]|$)|just lint|validate-change' . || true

echo
echo "== Justfile lint sections =="
rg -n -A 20 -B 5 '(^|[[:space:]])lint([[:space:]]|:)|ruff' justfile || true

echo
echo "== Target method complexity =="
python3 - <<'PY'
from pathlib import Path
import ast

path = Path("external/nat/src/nemo_fabric_adapters/nat/adapter.py")
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
    if isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef)) and node.name == "invoke_openai_stream":
        returns = sum(isinstance(n, ast.Return) for n in ast.walk(node))
        branches = sum(isinstance(n, (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try, ast.ExceptHandler, ast.With, ast.AsyncWith, ast.BoolOp)) for n in ast.walk(node))
        statements = sum(isinstance(n, ast.stmt) for n in ast.walk(node))
        print(f"function={node.name} lines={node.lineno}-{node.end_lineno} returns={returns} branches={branches} statements={statements}")
PY

Repository: NVIDIA/NeMo-Fabric

Length of output: 385


Consider extracting the stream-drain helper.

invoke_openai_stream handles schema validation, session setup, serialization, emission, aggregation, and cleanup. Extracting the drain and aggregation logic would improve maintainability while preserving ordering and cleanup semantics. Ruff does not enforce PLR0911, PLR0912, or PLR0915 in this repository.

Keep the LOGGER.error calls unchanged. LOGGER.exception could expose secrets through traceback output.

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 835-835: Too many return statements (7 > 6)

(PLR0911)


[warning] 835-835: Too many branches (21 > 12)

(PLR0912)


[warning] 835-835: Too many statements (65 > 50)

(PLR0915)


[warning] 854-854: Do not catch blind exception: Exception

(BLE001)


[warning] 855-858: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 883-887: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


[warning] 909-909: Abstract raise to an inner function

(TRY301)


[warning] 926-926: Do not catch blind exception: Exception

(BLE001)


[warning] 927-931: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 940-943: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 948-948: Do not catch blind exception: Exception

(BLE001)


[warning] 949-952: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 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 `@external/nat/src/nemo_fabric_adapters/nat/adapter.py` around lines 835 - 959,
Extract the inner stream-draining and response aggregation logic from
invoke_openai_stream into a focused private helper, including chunk
serialization, emit ordering, content collection, and stream cleanup semantics.
Have invoke_openai_stream call the helper while preserving cancellation and
lifecycle error propagation; leave all existing LOGGER.error calls unchanged.

Source: Linters/SAST tools

Comment thread tests/adapters/test_external_nat_adapter.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 12

♻️ Duplicate comments (1)
adapters/common/src/nemo_fabric_adapters/common/lifecycle.py (1)

236-245: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Parse the HTTP status line without requiring a reason phrase.

split(" ", 2) unpacks into exactly three names. A response line such as HTTP/1.1 200\r\n yields two parts and raises ValueError, so a valid response is reported as lifecycle_stream_transport_failed. RFC 9112 allows an empty reason phrase. Read the status by index.

🐛 Proposed fix
     status_line = await reader.readline()
     try:
-        _version, raw_status, _reason = status_line.decode("ascii").split(" ", 2)
-        status = int(raw_status)
+        parts = status_line.decode("ascii").split(" ", 2)
+        if len(parts) < 2:
+            raise ValueError("missing HTTP status code")
+        status = int(parts[1])
     except (UnicodeDecodeError, ValueError) as error:
🤖 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 `@adapters/common/src/nemo_fabric_adapters/common/lifecycle.py` around lines
236 - 245, Update _read_http_response to parse the HTTP version and status from
indexed fields rather than requiring a third reason-phrase field from split(" ",
2). Preserve integer status conversion and LifecycleError handling for malformed
or non-ASCII status lines, while accepting valid responses with no reason
phrase.
🤖 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 `@adapters/common/src/nemo_fabric_adapters/common/lifecycle.py`:
- Around line 302-374: The chunk validation logic is duplicated between
_validated_openai_chunk and _validate_openai_chunk, allowing the two OpenAI
streaming contracts to drift. Consolidate the shared field and bounds validation
into one importable predicate used by both validators, preserving each
validator’s existing exception type; if the distribution boundary prevents
sharing, add parity tests covering the same invalid-chunk corpus and rejection
behavior.

In `@crates/fabric-core/src/runtime.rs`:
- Around line 3072-3141: Update openai_stream_listener to configure a read
timeout on the accepted TcpStream before constructing the BufReader, using the
test’s expected timeout and asserting that set_read_timeout succeeds. Keep the
existing accept behavior and ensure subsequent read operations fail promptly
when the client does not connect fully.

In
`@docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx`:
- Around line 24-68: Update the enum struct-variant field heading generation in
generate_rust_library_reference.py so fields are emitted one level below their
variant’s Fields heading, rather than at the variant heading level. Preserve the
existing variant headings and apply the correction to all enum struct variants,
then run just docs to regenerate the affected reference page; do not edit
generated docs directly.

In `@external/nat/src/nemo_fabric_adapters/nat/adapter.py`:
- Around line 872-878: Extract the duplicated session-kwargs derivation,
including the is_workflow_per_user getattr probe, into a private helper on the
adapter. Update both invoke and invoke_openai_stream to call that helper,
preserving the existing request input and require_user_id behavior.

In `@python/src/nemo_fabric/openai_streaming.py`:
- Around line 142-158: The pending-connection limit in _accept allows
unauthenticated idle clients to consume every slot until
_OPENAI_STREAM_HEADER_TIMEOUT expires. Update the connection admission or
header-timeout handling around _accept and _handle_client so unauthenticated
connections cannot exhaust _MAX_PENDING_CONNECTIONS, while preserving the
existing rejection behavior for genuinely pending authenticated work.
- Around line 647-661: Add a narrow public error-setting method such as
set_error(message) to _OpenAIStreamListener that records the error without
setting the completion event, then update _validate_and_accept_result to use it
instead of accessing _set_error directly. Preserve the existing invocation-ID
validation and protocol-failure flow.
- Around line 271-285: Update the _emit_line and stream-record size error
messages to interpolate the configured self._max_record_bytes value instead of
the fixed “1 MiB” text, and update the corresponding test assertion in
test_openai_streaming.py to expect the configured limit.

In `@python/src/nemo_fabric/runtime.py`:
- Around line 116-121: Update Runtime.supports_openai_streaming in
python/src/nemo_fabric/runtime.py:116-121 to require both
plan.capabilities.streaming and
plan.adapter_descriptor.descriptor.capabilities.streaming, so unsupported stale
plans raise FabricCapabilityError before dispatch. In
tests/python/test_openai_streaming.py:846-853, add coverage for a
streaming-enabled plan with a non-streaming descriptor and assert the native
module is not called.

In `@tests/adapters/test_adapters_common_lifecycle.py`:
- Around line 132-136: Update the three listener.records.get() calls in the
lifecycle test to use asyncio.wait_for with the existing one-second timeout
pattern, ensuring each read fails promptly rather than hanging when fewer than
three records are emitted.

In `@tests/adapters/test_external_nat_adapter.py`:
- Around line 1342-1358: Update
test_openai_stream_normalizes_chunk_serialization_failure so the non-exception
serialization_failure parameter overrides the mock_nat["to_jsonable"] side
effect before assigning return_value, ensuring the test actually receives
["not", "a", "mapping"] and exercises the serializer-returned non-mapping path.

In `@tests/python/test_openai_streaming.py`:
- Around line 846-853: Add a test alongside
test_invoke_openai_stream_rejects_an_unsupported_adapter where _plan()
advertises streaming but adapter_descriptor.descriptor omits the streaming
capability. Assert runtime.invoke_openai_stream raises FabricCapabilityError
with the appropriate unavailable code and mock_native.invoke_openai_stream is
not called, verifying rejection occurs before the native module.
- Around line 484-489: Update the wait_for_end polling loop to replace
asyncio.sleep(0) with a short positive sleep interval, allowing the worker
thread to make progress while preserving the existing asyncio.wait_for timeout
and _end_observed termination condition.

---

Duplicate comments:
In `@adapters/common/src/nemo_fabric_adapters/common/lifecycle.py`:
- Around line 236-245: Update _read_http_response to parse the HTTP version and
status from indexed fields rather than requiring a third reason-phrase field
from split(" ", 2). Preserve integer status conversion and LifecycleError
handling for malformed or non-ASCII status lines, while accepting valid
responses with no reason phrase.
🪄 Autofix

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: ASSERTIVE

Plan: Enterprise

Run ID: 9de3831d-9e7c-49b0-9104-83888862c04f

📥 Commits

Reviewing files that changed from the base of the PR and between e551aa0 and 7ccb8c8.

📒 Files selected for processing (27)
  • adapters/common/src/nemo_fabric_adapters/common/lifecycle.py
  • crates/fabric-core/src/runtime.rs
  • crates/fabric-core/src/schema.rs
  • docs/reference/api/python-library-reference/nemo_fabric.openai_streaming.md
  • docs/reference/api/python-library-reference/nemo_fabric.runtime.md
  • docs/reference/api/python-library-reference/nemo_fabric.streaming.md
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaichatcompletionchunkobject.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamhost.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamprofile.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamprotocolversion.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunk.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunkchoice.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaichatcompletionchunkdelta.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreaminvocation.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamtransport.mdx
  • external/nat/README.md
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • python/src/nemo_fabric/openai_streaming.py
  • python/src/nemo_fabric/runtime.py
  • python/src/nemo_fabric/streaming.py
  • schemas/adapter-contract/legacy/openai-stream-record.schema.json
  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/adapters/test_external_nat_adapter.py
  • tests/python/test_openai_streaming.py

Comment on lines +302 to +374
def _validated_openai_chunk(value: dict[str, Any]) -> dict[str, Any]:
def invalid(message: str) -> LifecycleError:
return LifecycleError("lifecycle_invalid_openai_stream_event", message)

if value.get("object") != "chat.completion.chunk":
raise invalid("OpenAI stream events must use object 'chat.completion.chunk'")
identifier = value.get("id")
model = value.get("model")
created = value.get("created")
choices = value.get("choices")
if not isinstance(identifier, str) or not identifier.strip():
raise invalid(
"OpenAI stream event id must be a non-empty string containing "
"a non-whitespace character"
)
if not isinstance(model, str) or not model.strip():
raise invalid(
"OpenAI stream event model must be a non-empty string containing "
"a non-whitespace character"
)
if (
isinstance(created, bool)
or not isinstance(created, int)
or not 0 <= created <= _UINT64_MAX
):
raise invalid("OpenAI stream event created must be an unsigned 64-bit integer")
if not isinstance(choices, list):
raise invalid("OpenAI stream event choices must be a list")
for choice in choices:
if not isinstance(choice, dict):
raise invalid("OpenAI stream choices must be mappings")
index = choice.get("index")
delta = choice.get("delta")
if (
isinstance(index, bool)
or not isinstance(index, int)
or not 0 <= index <= _UINT32_MAX
):
raise invalid("OpenAI stream choice index must be an unsigned 32-bit integer")
if not isinstance(delta, dict):
raise invalid("OpenAI stream choice delta must be a mapping")
for name in ("content", "refusal", "role"):
if name in delta and delta[name] is not None and not isinstance(
delta[name], str
):
raise invalid(
f"OpenAI stream choice delta {name} must be a string or null"
)
if "function_call" in delta and delta["function_call"] is not None:
if not isinstance(delta["function_call"], dict):
raise invalid(
"OpenAI stream choice delta function_call must be a mapping or null"
)
if "tool_calls" in delta and delta["tool_calls"] is not None:
tool_calls = delta["tool_calls"]
if not isinstance(tool_calls, list) or not all(
isinstance(tool_call, dict) for tool_call in tool_calls
):
raise invalid(
"OpenAI stream choice delta tool_calls must be a list of mappings or null"
)
if "finish_reason" in choice and choice["finish_reason"] is not None:
if not isinstance(choice["finish_reason"], str):
raise invalid(
"OpenAI stream choice finish_reason must be a string or null"
)
if "logprobs" in choice and choice["logprobs"] is not None:
if not isinstance(choice["logprobs"], dict):
raise invalid("OpenAI stream choice logprobs must be a mapping or null")
if "usage" in value and value["usage"] is not None:
if not isinstance(value["usage"], dict):
raise invalid("OpenAI stream event usage must be a mapping or null")
return value

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Two copies of the chunk-profile validation now exist.

_validated_openai_chunk duplicates _validate_openai_chunk in python/src/nemo_fabric/openai_streaming.py lines 368-445, field for field, including the _UINT32_MAX and _UINT64_MAX bounds and the message wording. The two differ only in the exception type. The openai.chat_completions.chunk/v1 profile is one contract; two hand-maintained validators will drift, and a drift makes the adapter emit records that the SDK listener rejects mid-stream.

Generate both validators from the committed schema snapshot, or move the shared predicate into one module that both distributions import. If the packaging boundary blocks sharing, add a test that asserts both implementations reject the same corpus of invalid chunks.

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 302-302: Too many branches (21 > 12)

(PLR0912)


[warning] 307-307: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 313-316: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 318-321: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 327-327: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 329-329: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 332-332: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 340-340: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 342-342: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 347-349: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 350-351: Use a single if statement instead of nested if statements

(SIM102)


[warning] 352-354: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 360-362: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 363-364: Use a single if statement instead of nested if statements

(SIM102)


[warning] 365-367: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 368-369: Use a single if statement instead of nested if statements

(SIM102)


[warning] 370-370: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 371-372: Use a single if statement instead of nested if statements

(SIM102)


[warning] 373-373: Avoid specifying long messages outside the exception class

(TRY003)

🤖 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 `@adapters/common/src/nemo_fabric_adapters/common/lifecycle.py` around lines
302 - 374, The chunk validation logic is duplicated between
_validated_openai_chunk and _validate_openai_chunk, allowing the two OpenAI
streaming contracts to drift. Consolidate the shared field and bounds validation
into one importable predicate used by both validators, preserving each
validator’s existing exception type; if the distribution boundary prevents
sharing, add parity tests covering the same invalid-chunk corpus and rejection
behavior.

Comment on lines +3072 to +3141
fn openai_stream_listener(
token: &str,
) -> (OpenAiStreamTransport, thread::JoinHandle<Vec<Value>>) {
let listener = TcpListener::bind((OPENAI_STREAM_HOST, 0)).expect("bind stream listener");
let port = listener.local_addr().expect("listener address").port();
let expected_token = token.to_string();
let capture = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept stream connection");
let mut reader = BufReader::new(stream.try_clone().expect("clone stream"));
let mut headers = Vec::new();
loop {
let mut line = String::new();
reader.read_line(&mut line).expect("read HTTP header");
if line == "\r\n" {
break;
}
headers.push(line);
}
assert_eq!(
headers.first().map(String::as_str),
Some("POST /openai-stream HTTP/1.1\r\n")
);
assert!(headers.iter().any(|header| {
header == &format!("Authorization: Bearer {expected_token}\r\n")
}));
stream
.write_all(b"HTTP/1.1 100 Continue\r\n\r\n")
.expect("accept stream request");
stream.flush().expect("flush continue response");

let mut records = Vec::new();
loop {
let mut size_line = String::new();
reader.read_line(&mut size_line).expect("read chunk size");
let size = usize::from_str_radix(size_line.trim(), 16).expect("hex chunk size");
if size == 0 {
let mut terminator = String::new();
reader
.read_line(&mut terminator)
.expect("read chunk terminator");
assert_eq!(terminator, "\r\n");
break;
}
let mut encoded = vec![0; size];
reader.read_exact(&mut encoded).expect("read chunk body");
let mut terminator = [0; 2];
reader
.read_exact(&mut terminator)
.expect("read chunk terminator");
assert_eq!(&terminator, b"\r\n");
records.push(
serde_json::from_slice(encoded.strip_suffix(b"\n").unwrap_or(&encoded))
.expect("parse stream record"),
);
}
stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
.expect("complete stream request");
stream.flush().expect("flush final response");
records
});
(
OpenAiStreamTransport {
port,
token: token.to_string(),
},
capture,
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add socket timeouts to the test listener helper.

openai_stream_listener spawns a thread that blocks on listener.accept() and then on reader.read_line. No timeout is set. If a future change makes the adapter fail before it connects, or truncates the chunked body, capture.join() at the call site blocks forever and the test hangs instead of failing.

Set a read timeout on the accepted stream and assert on failure.

♻️ Proposed change
         let capture = thread::spawn(move || {
             let (mut stream, _) = listener.accept().expect("accept stream connection");
+            stream
+                .set_read_timeout(Some(Duration::from_secs(10)))
+                .expect("set stream read timeout");
             let mut reader = BufReader::new(stream.try_clone().expect("clone stream"));

TcpListener::accept itself cannot take a timeout directly; if you also want to bound the accept, set the listener non-blocking or keep the current behavior and rely on the harness timeout.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn openai_stream_listener(
token: &str,
) -> (OpenAiStreamTransport, thread::JoinHandle<Vec<Value>>) {
let listener = TcpListener::bind((OPENAI_STREAM_HOST, 0)).expect("bind stream listener");
let port = listener.local_addr().expect("listener address").port();
let expected_token = token.to_string();
let capture = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept stream connection");
let mut reader = BufReader::new(stream.try_clone().expect("clone stream"));
let mut headers = Vec::new();
loop {
let mut line = String::new();
reader.read_line(&mut line).expect("read HTTP header");
if line == "\r\n" {
break;
}
headers.push(line);
}
assert_eq!(
headers.first().map(String::as_str),
Some("POST /openai-stream HTTP/1.1\r\n")
);
assert!(headers.iter().any(|header| {
header == &format!("Authorization: Bearer {expected_token}\r\n")
}));
stream
.write_all(b"HTTP/1.1 100 Continue\r\n\r\n")
.expect("accept stream request");
stream.flush().expect("flush continue response");
let mut records = Vec::new();
loop {
let mut size_line = String::new();
reader.read_line(&mut size_line).expect("read chunk size");
let size = usize::from_str_radix(size_line.trim(), 16).expect("hex chunk size");
if size == 0 {
let mut terminator = String::new();
reader
.read_line(&mut terminator)
.expect("read chunk terminator");
assert_eq!(terminator, "\r\n");
break;
}
let mut encoded = vec![0; size];
reader.read_exact(&mut encoded).expect("read chunk body");
let mut terminator = [0; 2];
reader
.read_exact(&mut terminator)
.expect("read chunk terminator");
assert_eq!(&terminator, b"\r\n");
records.push(
serde_json::from_slice(encoded.strip_suffix(b"\n").unwrap_or(&encoded))
.expect("parse stream record"),
);
}
stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
.expect("complete stream request");
stream.flush().expect("flush final response");
records
});
(
OpenAiStreamTransport {
port,
token: token.to_string(),
},
capture,
)
}
fn openai_stream_listener(
token: &str,
) -> (OpenAiStreamTransport, thread::JoinHandle<Vec<Value>>) {
let listener = TcpListener::bind((OPENAI_STREAM_HOST, 0)).expect("bind stream listener");
let port = listener.local_addr().expect("listener address").port();
let expected_token = token.to_string();
let capture = thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("accept stream connection");
stream
.set_read_timeout(Some(Duration::from_secs(10)))
.expect("set stream read timeout");
let mut reader = BufReader::new(stream.try_clone().expect("clone stream"));
let mut headers = Vec::new();
loop {
let mut line = String::new();
reader.read_line(&mut line).expect("read HTTP header");
if line == "\r\n" {
break;
}
headers.push(line);
}
assert_eq!(
headers.first().map(String::as_str),
Some("POST /openai-stream HTTP/1.1\r\n")
);
assert!(headers.iter().any(|header| {
header == &format!("Authorization: Bearer {expected_token}\r\n")
}));
stream
.write_all(b"HTTP/1.1 100 Continue\r\n\r\n")
.expect("accept stream request");
stream.flush().expect("flush continue response");
let mut records = Vec::new();
loop {
let mut size_line = String::new();
reader.read_line(&mut size_line).expect("read chunk size");
let size = usize::from_str_radix(size_line.trim(), 16).expect("hex chunk size");
if size == 0 {
let mut terminator = String::new();
reader
.read_line(&mut terminator)
.expect("read chunk terminator");
assert_eq!(terminator, "\r\n");
break;
}
let mut encoded = vec![0; size];
reader.read_exact(&mut encoded).expect("read chunk body");
let mut terminator = [0; 2];
reader
.read_exact(&mut terminator)
.expect("read chunk terminator");
assert_eq!(&terminator, b"\r\n");
records.push(
serde_json::from_slice(encoded.strip_suffix(b"\n").unwrap_or(&encoded))
.expect("parse stream record"),
);
}
stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
.expect("complete stream request");
stream.flush().expect("flush final response");
records
});
(
OpenAiStreamTransport {
port,
token: token.to_string(),
},
capture,
)
}
🤖 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 `@crates/fabric-core/src/runtime.rs` around lines 3072 - 3141, Update
openai_stream_listener to configure a read timeout on the accepted TcpStream
before constructing the BufReader, using the test’s expected timeout and
asserting that set_read_timeout succeeds. Keep the existing accept behavior and
ensure subsequent read operations fail promptly when the client does not connect
fully.

Comment thread external/nat/src/nemo_fabric_adapters/nat/adapter.py
Comment on lines +142 to +158
def _accept(
self,
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
) -> None:
if len(self._tasks) >= _MAX_PENDING_CONNECTIONS:
writer.close()
return
task = asyncio.create_task(self._handle_client(reader, writer))
self._tasks.add(task)
task.add_done_callback(self._task_done)

def _task_done(self, task: asyncio.Task[None]) -> None:
self._tasks.discard(task)
if not task.cancelled():
task.exception()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Unauthenticated clients can occupy every accept slot.

_accept rejects new connections after _MAX_PENDING_CONNECTIONS tasks exist. A client that opens a socket and sends nothing holds a slot for _OPENAI_STREAM_HEADER_TIMEOUT (10 s). Eight such sockets block the adapter connection until the timeouts expire, and the invocation then fails with a protocol error.

The listener binds to 127.0.0.1, so the exposure is local only. Consider lowering the header timeout or excluding unauthenticated connections from the slot count.

🤖 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 `@python/src/nemo_fabric/openai_streaming.py` around lines 142 - 158, The
pending-connection limit in _accept allows unauthenticated idle clients to
consume every slot until _OPENAI_STREAM_HEADER_TIMEOUT expires. Update the
connection admission or header-timeout handling around _accept and
_handle_client so unauthenticated connections cannot exhaust
_MAX_PENDING_CONNECTIONS, while preserving the existing rejection behavior for
genuinely pending authenticated work.

Comment thread python/src/nemo_fabric/runtime.py
Comment thread tests/adapters/test_adapters_common_lifecycle.py
Comment on lines +1342 to +1358
@pytest.mark.parametrize(
"serialization_failure",
[TypeError("secret-object-repr"), ["not", "a", "mapping"]],
)
async def test_openai_stream_normalizes_chunk_serialization_failure(
make_payload,
make_invocation_payload,
mock_nat,
caplog,
serialization_failure: Any,
):
stream = _AsyncChunkStream([object()])
mock_nat["runner"].result_stream.return_value = stream
if isinstance(serialization_failure, BaseException):
mock_nat["to_jsonable"].side_effect = serialization_failure
else:
mock_nat["to_jsonable"].return_value = serialization_failure

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The non-exception parameter does not exercise the intended path.

The mock_nat fixture creates to_jsonable with side_effect=lambda value, **_kwargs: value. side_effect takes precedence over return_value unless it returns mock.DEFAULT. For the ["not", "a", "mapping"] parameter, Line 1358 sets return_value, but the mock still returns the original object() instance. The test passes only because object() is also not a dict, so the intended "serializer returned a non-mapping" case is never asserted.

Clear the side_effect so the parameter takes effect.

🐛 Proposed fix
     if isinstance(serialization_failure, BaseException):
         mock_nat["to_jsonable"].side_effect = serialization_failure
     else:
+        mock_nat["to_jsonable"].side_effect = None
         mock_nat["to_jsonable"].return_value = serialization_failure
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@pytest.mark.parametrize(
"serialization_failure",
[TypeError("secret-object-repr"), ["not", "a", "mapping"]],
)
async def test_openai_stream_normalizes_chunk_serialization_failure(
make_payload,
make_invocation_payload,
mock_nat,
caplog,
serialization_failure: Any,
):
stream = _AsyncChunkStream([object()])
mock_nat["runner"].result_stream.return_value = stream
if isinstance(serialization_failure, BaseException):
mock_nat["to_jsonable"].side_effect = serialization_failure
else:
mock_nat["to_jsonable"].return_value = serialization_failure
`@pytest.mark.parametrize`(
"serialization_failure",
[TypeError("secret-object-repr"), ["not", "a", "mapping"]],
)
async def test_openai_stream_normalizes_chunk_serialization_failure(
make_payload,
make_invocation_payload,
mock_nat,
caplog,
serialization_failure: Any,
):
stream = _AsyncChunkStream([object()])
mock_nat["runner"].result_stream.return_value = stream
if isinstance(serialization_failure, BaseException):
mock_nat["to_jsonable"].side_effect = serialization_failure
else:
mock_nat["to_jsonable"].side_effect = None
mock_nat["to_jsonable"].return_value = serialization_failure
🧰 Tools
🪛 Ruff (0.16.1)

[warning] 1351-1351: Dynamically typed expressions (typing.Any) are disallowed in serialization_failure

(ANN401)

🤖 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 `@tests/adapters/test_external_nat_adapter.py` around lines 1342 - 1358, Update
test_openai_stream_normalizes_chunk_serialization_failure so the non-exception
serialization_failure parameter overrides the mock_nat["to_jsonable"] side
effect before assigning return_value, ensuring the test actually receives
["not", "a", "mapping"] and exercises the serializer-returned non-mapping path.

Comment on lines +484 to +489
async def wait_for_end() -> None:
while not stream._end_observed:
await asyncio.sleep(0)

try:
await asyncio.wait_for(wait_for_end(), timeout=1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the zero-delay spin with a small sleep.

asyncio.sleep(0) yields without delay, so the loop saturates the event loop for up to the full one-second timeout and starves the worker thread that feeds the listener. The asyncio.wait_for bound already prevents a hang. Use a short sleep interval.

♻️ Proposed fix
     async def wait_for_end() -> None:
         while not stream._end_observed:
-            await asyncio.sleep(0)
+            await asyncio.sleep(0.001)

Based on static analysis hints from Ruff 0.16.1 (ASYNC110).

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 485-486: Use asyncio.Event instead of awaiting asyncio.sleep in a while loop

(ASYNC110)

🤖 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 `@tests/python/test_openai_streaming.py` around lines 484 - 489, Update the
wait_for_end polling loop to replace asyncio.sleep(0) with a short positive
sleep interval, allowing the worker thread to make progress while preserving the
existing asyncio.wait_for timeout and _end_observed termination condition.

Source: Linters/SAST tools

Comment thread tests/python/test_openai_streaming.py
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
Signed-off-by: Ajay Thorve <athorve@nvidia.com>
@coderabbitai coderabbitai Bot mentioned this pull request Aug 11, 2026
2 tasks
@AjayThorve
AjayThorve force-pushed the feat/nat-openai-streaming branch from 7ccb8c8 to 99174ab Compare August 11, 2026 16:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@tests/adapters/test_adapters_common_lifecycle.py`:
- Around line 30-51: Replace the hand-written _BackpressuredStreamWriter,
CancellingWriter, and FailingWriter doubles with unittest.mock
AsyncMock/MagicMock instances, using spec=asyncio.StreamWriter where
appropriate. Preserve backpressure coordination through the backpressured mock’s
drain side_effect and retain the cancellation and failure behavior through
side_effect callbacks, naming any mock variables with the mock prefix.

In `@tests/adapters/test_external_nat_adapter.py`:
- Around line 791-796: Update the test around SessionManager.create to require
NAT version 1.5.0 or skip unsupported releases before accessing
is_workflow_per_user and get_workflow_streaming_output_schema. Preserve the
existing missing-module skip behavior and ensure older versions do not fail with
AttributeError.
🪄 Autofix

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: ASSERTIVE

Plan: Enterprise

Run ID: d6b14f0a-d82d-4d6b-9747-3589b37d461e

📥 Commits

Reviewing files that changed from the base of the PR and between 7ccb8c8 and 99174ab.

📒 Files selected for processing (14)
  • crates/fabric-core/src/runtime.rs
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • python/src/nemo_fabric/runtime.py
  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/adapters/test_external_nat_adapter.py
  • tests/python/test_openai_streaming.py
📜 Review details
⏰ Context from checks skipped due to timeout. (17)
  • GitHub Check: Preview docs
  • GitHub Check: Pre-commit
  • GitHub Check: Test (Python 3.14, linux-arm64)
  • GitHub Check: Test (Python 3.14, windows-amd64)
  • GitHub Check: Test (Python 3.14, linux-amd64)
  • GitHub Check: Test (Python 3.13, macos-arm64)
  • GitHub Check: Test (Python 3.12, windows-amd64)
  • GitHub Check: Test (Python 3.13, linux-amd64)
  • GitHub Check: Test (Python 3.13, linux-arm64)
  • GitHub Check: Test (Python 3.12, macos-arm64)
  • GitHub Check: Test (Python 3.11, linux-amd64)
  • GitHub Check: Test (Python 3.11, macos-arm64)
  • GitHub Check: Test (Python 3.11, linux-arm64)
  • GitHub Check: Test (Python 3.14, macos-arm64)
  • GitHub Check: Test (Python 3.12, linux-amd64)
  • GitHub Check: Test (Python 3.11, windows-amd64)
  • GitHub Check: Test (Python 3.12, linux-arm64)
🧰 Additional context used
📓 Path-based instructions (36)
{README.md,docs/**/*.{md,mdx,yml},examples/**/*.{md,mdx,yml}}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Keep package names, repository references, and build commands current in documentation and examples.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
{docs/**/*.{md,mdx,yml},examples/**/*.{md,mdx,yml}}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Update relevant getting-started, reference, adapter, and example documentation when the corresponding examples or adapters change.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

In MDX files, use JSX comment delimiters ({/* and */}) for top-of-file comments, including SPDX headers; do not use HTML comments.

**/*.mdx: For documentation site changes, run just docs to regenerate Python and Rust API references and validate Fern configuration.
MDX files must use the specified JSX-comment SPDX header.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
docs/**/*.{md,mdx,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Run just docs when the documentation site changes.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
**/*

📄 CodeRabbit inference engine (.agents/skills/karpathy-guidelines/SKILL.md)

**/*: Before implementing, explicitly state assumptions, surface ambiguity and tradeoffs, present multiple interpretations when relevant, and ask for clarification rather than silently deciding or proceeding when requirements are unclear.
Prefer the minimum code needed to solve the requested problem: avoid speculative features, unnecessary abstractions, unrequested flexibility, and handling of impossible scenarios; simplify overcomplicated solutions.
When editing existing code, make surgical changes only: do not modify unrelated code, comments, formatting, or pre-existing dead code; match the existing style, and remove only unused imports, variables, or functions introduced by your changes.
Define verifiable success criteria for each task, such as writing regression tests for bugs and invalid-input tests for validation, then verify the implementation against those criteria. For multi-step work, state a brief plan with a verification check for each step.

**/*: Always spell NVIDIA in all caps; do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun, because the name begins with an “en” sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company; use trademark symbols with product names only when required by the document type or legal guidance.
Verify official capitalization, spacing, hyphenation, and spelling for NVIDIA and third-party product names; do not rewrite official product names for grammar or title-case rules.
Precede NVIDIA product names with NVIDIA on first mention when natural and accurate, and link the first mention when the destination helps the reader.
On first use, include the company name and full model qualifier when it helps identify the model; preserve official capitalization and punctuation, and use shorter family names only after establishing the full name.
For learning-oriented and developer content, do not force trademark symbols unless explicitly required; for press, ...

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • scripts/docs/generate_rust_library_reference.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • tests/adapters/test_adapters_common_lifecycle.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
  • crates/fabric-core/src/runtime.rs
**/*.{md,mdx,rst}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-guide.md)

**/*.{md,mdx,rst}: For NeMo Fabric documentation, verify technical claims against the current repository, public API, or documented command before reviewing style.
Always spell NVIDIA in all caps; do not use Nvidia, nvidia, or NV.
Format commands, code elements, expressions, package names, file names, and paths as inline code.
Use descriptive link text; avoid raw URLs and weak anchors such as here or read more.
Use title case consistently for technical documentation headings.
Introduce code blocks, lists, tables, and images with complete sentences.
Write procedures as imperative, parallel steps; split long procedures into smaller tasks.
Prefer active voice, present tense, short sentences, contractions, and plain English while preserving necessary technical precision.
Use can for possibility and reserve may for permission.
Use after for temporal relationships instead of once, and prefer refer to over see when directing readers to another resource.
Avoid culture-specific idioms, unnecessary Latinisms, jokes, and marketing exaggeration in technical documentation.
Spell out months in body text, avoid ordinal dates, and use clear time zones.
Spell out whole numbers from zero through nine unless they are technical values, parameters, versions, or UI values; use numerals for 10 or greater and commas in thousands.
Do not add trademark symbols to learning-oriented documentation unless the source, platform, or legal guidance explicitly requires them.
Do not replace precise technical terms with simpler words when doing so would lose precision.
Do not flag passive voice when the actor is unknown or the action is the important part.
Do not rewrite API names, package names, command flags, or code literals for style.

**/*.{md,mdx,rst}: Use consistent title case for technical-document headings and table headers; avoid quotation marks, ampersands, and exclamation marks in headings, while preserving official product, event, research, and whitepaper title ...

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
docs/reference/api/**/*

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Treat all files under docs/reference/api/ as generated output and do not modify them directly.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
docs/**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

docs/**/*.mdx: Use source-relative links with the target .mdx extension for links between files under docs/; do not use Fern site-root paths.
Use {/* ... */} delimiters for top-of-file MDX SPDX comments, not HTML comment delimiters.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
**/*.{md,mdx,yml,py,rs,sh}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

Keep documentation aligned with current NeMo Fabric behavior, repository layout, entry points, commands, package names, APIs, bindings, and support claims.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • scripts/docs/generate_rust_library_reference.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • tests/adapters/test_adapters_common_lifecycle.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
  • crates/fabric-core/src/runtime.rs
**/*.{md,mdx}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

**/*.{md,mdx}: Use the full product name NVIDIA NeMo Fabric on first use, typically in the title and H1; use NeMo Fabric thereafter. Use fabric alone only for the CLI tool and surround it with backticks.
Treat incorrect or stale commands, package names, paths, APIs, support claims, procedures, examples, terminology, or public behavior documentation as blocking issues.
Capitalize NVIDIA correctly and format code, commands, paths, and filenames as inline code where needed.
Use title case for technical-documentation headings.
Introduce code blocks, tables, and lists with complete lead-in sentences; ensure examples match current APIs and build commands.
Use descriptive anchor text, avoid raw URLs and generic labels such as here, and use repository-relative .mdx paths for links within docs/.
Prefer active voice, present tense, short sentences, plain English, consistent terminology, and imperative, parallel, scannable procedures.
Use after instead of once when expressing temporal sequence, and use can rather than may when describing possibility rather than permission.
Avoid ambiguous numeric dates and ordinal dates in body text.
For learning-oriented documentation, do not force trademark symbols unless the source document explicitly requires them.
When reporting documentation-review findings, lead with Must fix, Should fix, and Nice to have categories; include file path, line reference, current problem, rationale, and a concrete rewrite or direction.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
docs/**

📄 CodeRabbit inference engine (AGENTS.md)

Run just docs after changing the documentation site.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
{docs/**,README.md,AGENTS.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,AGENTS.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency with generated schemas.
For links between files under docs/, require paths relative to the source file with the target file's .mdx extension so they work in both Fern builds and repository browsers. Flag Fern site-root links such as NeMo Fabric overview; use the repository-relative equivalent, such as NeMo Fabric overview.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
{*.md,**/*.md,**/*.mdx,**/*.ipynb}

⚙️ CodeRabbit configuration file

{*.md,**/*.md,**/*.mdx,**/*.ipynb}: Enforce the product name in user-facing prose: use "NVIDIA NeMo Fabric" on first use and "NeMo Fabric" thereafter. Flag standalone capitalized "Fabric" when it refers to the product. Do not flag the lowercase fabric CLI command, package/import/crate names, code identifiers, API symbols, configuration keys, file paths, or unrelated generic uses of the word.

Files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
**/*.{rs,py,pyi,json,yaml,yml}

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Determine and update every affected public surface, including the CLI, PyO3 bindings, Python SDK, type stubs, schemas, and adapter contract, so they remain in parity.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • crates/fabric-core/src/runtime.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For native binding changes, run cargo check -p fabric-python --locked.

Use snake_case for functions and variables; use PascalCase for Rust types and Python classes.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • crates/fabric-core/src/runtime.rs
**/*.{py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Python code or a Python-facing adapter changes, run just test-python.

In Python SDK, adapters, examples, and tests, follow the existing style, use type annotations for public APIs, and keep native binding declarations synchronized with their Rust implementations.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
**/*.{rs,py,pyi}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{rs,py,pyi}: If public configuration types change, confirm schema snapshot tests in just test-rust pass and review generated schema diffs.
For schema or public contract changes, run both language suites and review changes under schemas/ and generated API references.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • crates/fabric-core/src/runtime.rs
**/*.{py,pyi,rs}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

For Python SDK or PyO3 binding changes, use python-tests, run focused pytest tests first, then just test-python; rebuild with just build-python when native code or packaging changes.

Public contract changes must keep native Python binding declarations synchronized with their Rust implementations.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • crates/fabric-core/src/runtime.rs
**/*.{rs,py,toml}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

When editing version helpers, verify every nemo-fabric-* workspace package through Cargo metadata and reject a static version in python/pyproject.toml.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • crates/fabric-core/src/runtime.rs
**/*.{toml,rs,py}

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Avoid blind repository-wide replacement of version-like strings; distinguish package-version references from examples and unrelated dependency versions.

Files:

  • scripts/docs/generate_rust_library_reference.py
  • tests/adapters/test_adapters_common_lifecycle.py
  • python/src/nemo_fabric/runtime.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
  • external/nat/src/nemo_fabric_adapters/nat/adapter.py
  • crates/fabric-core/src/runtime.rs
scripts/docs/generate_rust_library_reference.py

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

For Rust API reference changes, update scripts/docs/generate_rust_library_reference.py when the generator itself must change.

Files:

  • scripts/docs/generate_rust_library_reference.py
tests/adapters/**/*.py

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

tests/adapters/**/*.py: If an adapter or integration changes, run its focused tests.
For adapter behavior changes, run focused adapter tests under tests/adapters, then run just test-python.

Files:

  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/adapters/test_external_nat_adapter.py
tests/**/*.{rs,py}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the corresponding Rust crate or the relevant area under tests/.

Files:

  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/python-tests/SKILL.md)

tests/**/*.py: Use pytest to run Python tests.
Do not add @pytest.mark.asyncio to tests; async tests are automatically detected by the async runner.
Do not add -> None return type annotations to test functions.
When mocking a class, use unittest.mock.MagicMock or AsyncMock, using the spec argument when necessary, rather than defining a new class.
Prefix mocked class names with mock, not fake.
Prefer pytest fixtures over helper methods.
If a fixture is needed in multiple test files, define it once in conftest.py rather than repeating it.
Define fixtures using @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and a <fixture_name>_fixture function; specify scope only when it is not function.
Prefer pytest.mark.parametrize over separate tests for different input types.
Use @pytest.mark.usefixtures when a fixture is needed but its returned value is unused or it returns no value.
Avoid defensive programming in tests; access expected values directly so missing data raises a clear failure, such as using results["data"] instead of results.get("data").
When adapter installation metadata changes, packaging metadata tests must directly assert that the root project depends unconditionally on the exact-version nemo-fabric-runtime distribution.
Packaging metadata tests must verify that each root harness extra delegates to the matching version of the leaf adapter's harness extra.
Packaging metadata tests must verify that bare leaf dependencies remain adapter-owned and that the root adapter-tests dependency group installs each leaf through its harness extra.
Packaging metadata tests must verify that every leaf provides full; only adapters importing NeMo Relay Python APIs provide relay, while adapters using an external Relay executable have full equal to harness.

Files:

  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
{tests/**,python/tests/**}

⚙️ CodeRabbit configuration file

{tests/**,python/tests/**}: Tests should cover the behavior promised by the changed API surface, including error paths, lifecycle cleanup, and SDK/native parity where relevant.

Files:

  • tests/adapters/test_adapters_common_lifecycle.py
  • tests/python/test_openai_streaming.py
  • tests/adapters/test_external_nat_adapter.py
**/*.{json,jsonschema}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Public contract changes must keep checked-in JSON Schema snapshots synchronized.

Files:

  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
schemas/**/*

⚙️ CodeRabbit configuration file

schemas/**/*: Schemas are generated public contract snapshots. Check that schema diffs correspond to intentional Rust type changes and are covered by core tests.

Files:

  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
python/src/nemo_fabric/**/*.py

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

For Python API reference changes, update source docstrings under python/src/nemo_fabric/ instead of generated API reference files.

Files:

  • python/src/nemo_fabric/runtime.py
python/src/nemo_fabric/**/*.{py,rs}

📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)

Ensure native extension naming and placement remain compatible with downstream consumers, including the editable maturin build producing nemo_fabric._native.

Files:

  • python/src/nemo_fabric/runtime.py
python/src/nemo_fabric/**/*

⚙️ CodeRabbit configuration file

python/src/nemo_fabric/**/*: Review Python SDK changes for typed API consistency, import-time dependency neutrality, async/session behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/src/nemo_fabric/runtime.py
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/contribute-api/SKILL.md)

Implement new runtime or binding behavior in the shared Rust core first.

Files:

  • crates/fabric-core/src/runtime.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

For any Rust change, run just test-rust and cargo fmt --all -- --check.

For Rust core, CLI, or shared runtime semantic changes, run Rust formatting and tests, and add Python tests when behavior is exposed through the SDK.

Use Rust stable tooling; format Rust code with cargo fmt --all, verify formatting with cargo fmt --all -- --check, and compile with cargo check --workspace --locked.

Files:

  • crates/fabric-core/src/runtime.rs
crates/fabric-core/**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes under crates/fabric-core must run both the Rust and Python test suites.

Files:

  • crates/fabric-core/src/runtime.rs
**/*.{rs,rmeta}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If Rust code changes, run cargo fmt --all -- --check and just test-rust.

Files:

  • crates/fabric-core/src/runtime.rs
crates/fabric-core/**/*.rs

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If crates/fabric-core changes in a way exposed through Python, run both the Rust and Python suites.

For Rust API reference changes, update Rust documentation comments under crates/fabric-core/ instead of generated API reference files.

Files:

  • crates/fabric-core/src/runtime.rs
crates/fabric-core/src/**/*.rs

⚙️ CodeRabbit configuration file

crates/fabric-core/src/**/*.rs: Review the Rust core for runtime lifecycle correctness, handle validation, capability routing accuracy, schema stability, and error semantics.
Public API changes should match committed schemas, tests, and documentation.

Files:

  • crates/fabric-core/src/runtime.rs
🧠 Learnings (3)
📚 Learning: 2026-07-24T16:07:22.255Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 118
File: docs/reference/api/rust-library-reference/nemo-fabric-core/config/struct-adapterdescriptor.mdx:5-5
Timestamp: 2026-07-24T16:07:22.255Z
Learning: In this repo, files generated under `docs/reference/api/**` are NVIDIA NeMo Fabric API reference output. When reviewing changes to these generated pages, do not treat sidebar `position`/ordering updates as direct manual edits—these can be regenerated by running `just docs` after adding public types. Only flag substantive content changes that are not explained by generation.

Applied to files:

  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx
  • docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx
📚 Learning: 2026-08-07T07:15:33.918Z
Learnt from: AnuradhaKaruppiah
Repo: NVIDIA/NeMo-Fabric PR: 186
File: schemas/adapter-contract/legacy/adapter-invocation.schema.json:176-176
Timestamp: 2026-08-07T07:15:33.918Z
Learning: For the NeMo Fabric v1alpha southbound adapter contract, treat the adapter descriptor's `contract_version` as the version of the complete contract, including `RuntimeContext`. Keep `RuntimeContext` strict by rejecting unknown properties, and require a negotiated contract-version change for additive shape changes.

Applied to files:

  • schemas/adapter-contract/legacy/openai-stream-invocation.schema.json
📚 Learning: 2026-06-28T04:03:32.877Z
Learnt from: AjayThorve
Repo: NVIDIA/NeMo-Fabric PR: 26
File: python/tests/smoke_typed_config.py:163-177
Timestamp: 2026-06-28T04:03:32.877Z
Learning: In NVIDIA NeMo Fabric Python SDK serialization of `RuntimeCapabilities` (to satisfy the “parity contract” with Rust core and the CLI), do not emit metadata keys when the corresponding metadata is absent. Instead, omit those fields entirely so the produced JSON matches the Rust/CLI output (e.g., avoid `null`, empty objects, or placeholder metadata). During review, verify the serializer/builders follow this omission rule and that Python outputs/parity tests reflect the same shape.

Applied to files:

  • python/src/nemo_fabric/runtime.py
🪛 ast-grep (0.45.1)
python/src/nemo_fabric/runtime.py

[info] 194-194: use jsonify instead of json.dumps for JSON output
Context: json.dumps(self._runtime.to_mapping())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 195-195: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 207-207: use jsonify instead of json.dumps for JSON output
Context: json.dumps(dict(openai_stream_transport))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/python/test_openai_streaming.py

[info] 206-206: use jsonify instead of json.dumps for JSON output
Context: json.dumps(record, separators=(",", ":"))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 224-224: use jsonify instead of json.dumps for JSON output
Context: json.dumps(record, separators=(",", ":"))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 253-253: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_result(request, runtime, invocation_id=invocation_id))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 263-269: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(
request,
runtime,
invocation_id=f"invocation-{len(mock_native.requests)}",
)
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 327-329: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(request, runtime, invocation_id="invocation-empty")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 365-367: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(request, runtime, invocation_id="invocation-many")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 385-387: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(request, runtime, invocation_id="invocation-without-stream")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 417-419: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(request, runtime, invocation_id="invocation-after-probe")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 453-455: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(request, runtime, invocation_id="invocation-late-stream")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 483-489: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(
request,
runtime,
invocation_id="invocation-cancel-after-end",
)
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 556-563: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(
request,
runtime,
invocation_id="invocation-failed",
failed=True,
)
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 586-588: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_result(request, runtime, invocation_id="invocation-terminal")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 675-675: use jsonify instead of json.dumps for JSON output
Context: json.dumps(record)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 689-689: use jsonify instead of json.dumps for JSON output
Context: json.dumps(nonfinite)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 703-703: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_record())
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 706-712: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_record(
sequence=1,
invocation_id="invocation-other",
chunk=_chunk("chunk-2", "other"),
)
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 796-798: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
_record(record_type="end"), separators=(",", ":")
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

tests/adapters/test_external_nat_adapter.py

[info] 1367-1367: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 1405-1405: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 LanguageTool
docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx

[grammar] ~26-~26: Ensure spelling is correct
Context: ...reaminvocation.mdx): One adapter-native OpenAI streaming invocation. - [OpenAiStreamSink](struct-openaistrea...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~41-~41: Ensure spelling is correct
Context: ...num-openaistreamprofile.mdx): Supported OpenAI-compatible chunk profile. - [OpenAiStreamProtocolVersion](enum-openai...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~50-~50: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...for adapter-native OpenAI streaming. - [OPENAI_STREAM_PROTOCOL_VERSION](constant-opena...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~50-~50: Ensure spelling is correct
Context: ...und protocol version for adapter-native OpenAI streaming. ## Functions - [invoke_openai_stream](fn-i...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Ruff (0.16.1)
tests/adapters/test_adapters_common_lifecycle.py

[warning] 94-94: Missing return type annotation for private function start

Add return type annotation: None

(ANN202)


[warning] 97-97: Missing return type annotation for private function invoke

Add return type annotation: Never

(ANN202)


[warning] 98-98: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 100-100: Missing return type annotation for private function invoke_openai_stream

(ANN202)


[warning] 122-122: Missing return type annotation for private function stop

Add return type annotation: None

(ANN202)


[warning] 251-251: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 255-255: Missing return type annotation for private function open_connection

(ANN202)


[warning] 255-255: Missing type annotation for *_args

(ANN002)


[warning] 255-255: Missing type annotation for **_kwargs

(ANN003)


[warning] 290-290: Missing return type annotation for private function connect

(ANN202)


[warning] 299-299: Missing return type annotation for private function invoke_openai_stream

Add return type annotation: Never

(ANN202)


[warning] 341-341: Missing return type annotation for private function start

Add return type annotation: None

(ANN202)


[warning] 344-344: Missing return type annotation for private function invoke

(ANN202)


[warning] 347-347: Missing return type annotation for private function stop

Add return type annotation: None

(ANN202)


[warning] 432-432: Missing return type annotation for private function start

Add return type annotation: None

(ANN202)


[warning] 435-435: Missing return type annotation for private function invoke

Add return type annotation: None

(ANN202)


[warning] 438-438: Missing return type annotation for private function stop

Add return type annotation: None

(ANN202)

python/src/nemo_fabric/runtime.py

[error] 307-307: Function argument input is shadowing a Python builtin

(A002)


[warning] 307-307: Dynamically typed expressions (typing.Any) are disallowed in input

(ANN401)


[warning] 324-329: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 331-334: Avoid specifying long messages outside the exception class

(TRY003)

tests/python/test_openai_streaming.py

[warning] 19-19: Use from nemo_fabric import openai_streaming in lieu of alias

Replace with from nemo_fabric import openai_streaming

(PLR0402)


[warning] 125-125: Dynamically typed expressions (typing.Any) are disallowed in sequence

(ANN401)


[warning] 143-143: Dynamically typed expressions (typing.Any) are disallowed in stream

(ANN401)


[warning] 185-185: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 318-318: Missing return type annotation for private function invoke_empty

(ANN202)


[warning] 356-356: Missing return type annotation for private function invoke_many

(ANN202)


[warning] 383-383: Missing return type annotation for private function invoke_without_stream

(ANN202)


[warning] 406-406: Missing return type annotation for private function invoke_after_probe

(ANN202)


[error] 410-410: Possible hardcoded password assigned to argument: "token"

(S106)


[warning] 436-436: Missing return type annotation for private function invoke_before_stream

(ANN202)


[warning] 473-473: Missing return type annotation for private function invoke_after_end

(ANN202)


[warning] 498-499: Use asyncio.Event instead of awaiting asyncio.sleep in a while loop

(ASYNC110)


[warning] 547-547: Missing return type annotation for private function invoke_failed

(ANN202)


[warning] 577-577: Missing return type annotation for private function invoke_mismatch

(ANN202)


[warning] 718-718: Missing return type annotation for private function invoke_without_end

Add return type annotation: Never

(ANN202)


[warning] 731-731: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 743-743: Missing return type annotation for private function invoke_invalid_chunk

Add return type annotation: Never

(ANN202)


[warning] 759-759: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 781-781: Missing return type annotation for private function candidate

(ANN202)


[warning] 846-846: Boolean-typed positional argument in function definition

(FBT001)


[warning] 847-847: Boolean-typed positional argument in function definition

(FBT001)


[warning] 887-887: Missing return type annotation for private function fail_create_task

Add return type annotation: Never

(ANN202)


[warning] 889-889: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 891-891: Missing return type annotation for private function invoke

Add return type annotation: Never

(ANN202)


[warning] 892-892: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 920-920: Missing return type annotation for private function invoke_with_bad_token

Add return type annotation: Never

(ANN202)


[error] 929-929: Possible hardcoded password assigned to argument: "token"

(S106)


[warning] 931-931: Avoid specifying long messages outside the exception class

(TRY003)

tests/adapters/test_external_nat_adapter.py

[warning] 45-45: Missing return type annotation for special method __aiter__

(ANN204)


[warning] 48-48: Missing return type annotation for special method __anext__

(ANN204)


[warning] 466-466: Boolean-typed positional argument in function definition

(FBT001)


[warning] 1381-1381: Dynamically typed expressions (typing.Any) are disallowed in serialization_failure

(ANN401)

external/nat/src/nemo_fabric_adapters/nat/adapter.py

[warning] 663-666: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 844-844: Too many return statements (7 > 6)

(PLR0911)


[warning] 844-844: Too many branches (21 > 12)

(PLR0912)


[warning] 844-844: Too many statements (65 > 50)

(PLR0915)


[warning] 863-863: Do not catch blind exception: Exception

(BLE001)


[warning] 864-867: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 894-898: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


[warning] 920-920: Abstract raise to an inner function

(TRY301)


[warning] 937-937: Do not catch blind exception: Exception

(BLE001)


[warning] 938-942: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 951-954: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


[warning] 959-959: Do not catch blind exception: Exception

(BLE001)


[warning] 960-963: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🔇 Additional comments (29)
crates/fabric-core/src/runtime.rs (5)

3076-3145: Set a read timeout on the accepted stream.

openai_stream_listener blocks on accept() and read_line with no timeout. If a regression prevents the adapter from completing the chunked body, capture.join() blocks forever and the test hangs instead of failing.

♻️ Proposed change
         let capture = thread::spawn(move || {
             let (mut stream, _) = listener.accept().expect("accept stream connection");
+            stream
+                .set_read_timeout(Some(Duration::from_secs(10)))
+                .expect("set stream read timeout");
             let mut reader = BufReader::new(stream.try_clone().expect("clone stream"));

40-46: LGTM!

Also applies to: 349-560, 576-587, 618-630, 675-681


823-869: LGTM!


1227-1297: LGTM!

Also applies to: 1332-1362, 2130-2160


2840-2841: LGTM!

Also applies to: 2886-2886, 2912-2957, 2972-2972, 2983-3009, 3029-3029, 3237-3394

tests/python/test_openai_streaming.py (3)

497-499: Replace the zero-delay spin with a short sleep.

asyncio.sleep(0) yields without delay, so this loop saturates the event loop for up to the full one-second timeout and starves the worker thread that feeds the listener. Use await asyncio.sleep(0.001).


31-62: LGTM!

Also applies to: 231-295, 772-811, 840-881


112-228: LGTM!

Also applies to: 298-476, 517-714, 717-769, 884-941

python/src/nemo_fabric/runtime.py (2)

25-25: LGTM!

Also applies to: 76-76, 176-178, 194-216, 250-251, 269-270, 304-352, 382-383


116-133: 🩺 Stability & Availability

Retain the mapping lookup for adapter_descriptor; RunPlan inherits Mapping.get() and preserves this extension field.

			> Likely an incorrect or invalid review comment.
tests/adapters/test_adapters_common_lifecycle.py (2)

15-15: LGTM!

Also applies to: 54-157


160-228: LGTM!

Also applies to: 231-311, 314-451

docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatofsinkconfig.mdx (1)

26-38: LGTM!

Also applies to: 50-78

docs/reference/api/rust-library-reference/nemo-fabric-core/error/enum-fabricerror.mdx (2)

12-12: LGTM!

Also applies to: 288-327


26-276: LGTM!

Also applies to: 336-514

scripts/docs/generate_rust_library_reference.py (2)

399-401: LGTM!


514-514: 🎯 Functional Correctness

Keep the existing title normalization. Generated page titles already use OpenAI casing.

			> Likely an incorrect or invalid review comment.
tests/adapters/test_external_nat_adapter.py (3)

1385-1388: 🎯 Functional Correctness | ⚡ Quick win

Clear the mock side effect before setting return_value.

The mock_nat fixture sets to_jsonable with side_effect=lambda value, **_kwargs: value. A side_effect callable takes precedence over return_value unless it returns mock.DEFAULT. For the ["not", "a", "mapping"] parameter, the mock still returns the original object(). The test passes only because object() is also not a dict, so the "serializer returned a non-mapping" path is never exercised.

🐛 Proposed fix
     if isinstance(serialization_failure, BaseException):
         mock_nat["to_jsonable"].side_effect = serialization_failure
     else:
+        mock_nat["to_jsonable"].side_effect = None
         mock_nat["to_jsonable"].return_value = serialization_failure

32-62: LGTM!

Also applies to: 157-229, 289-343


1115-1200: LGTM!

Also applies to: 1203-1295, 1298-1334, 1337-1370, 1410-1454, 1457-1512, 1515-1547, 1550-1566, 1569-1604

external/nat/src/nemo_fabric_adapters/nat/adapter.py (3)

879-885: 📐 Maintainability & Code Quality | 💤 Low value

Deduplicate the per-user session-kwargs derivation.

Lines 880-885 repeat the block at Lines 799-804, including the getattr(self._sessions, "is_workflow_per_user", False) probe. Extract one private method and call it from invoke and invoke_openai_stream.


9-11: LGTM!

Also applies to: 34-44, 62-65, 121-126, 223-224, 235-235, 645-677, 728-752, 794-806


844-970: LGTM!

schemas/adapter-contract/legacy/openai-stream-invocation.schema.json (2)

170-179: 🗄️ Data Integrity & Integration | 🏗️ Heavy lift

Contract-version negotiation for InvokeOpenaiStream remains unresolved.

OpenAiStreamProtocolVersion describes only the stream wire protocol. It cannot negotiate support for the new lifecycle operation itself, because descriptors still declare the previous adapter contract version and the core dispatches on the streaming capability alone. Bump the adapter contract version, update descriptors and snapshots, and reject the older version before lifecycle dispatch.


180-239: LGTM!

Also applies to: 344-368

docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/struct-openaistreamsink.mdx (1)

1-123: LGTM!

docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/enum-openaistreamrecord.mdx (1)

1-143: LGTM!

docs/reference/api/rust-library-reference/nemo-fabric-core/runtime/index.mdx (1)

5-5: LGTM!

Also applies to: 23-28, 39-54

docs/reference/api/rust-library-reference/nemo-fabric-core/config/enum-relayatifstorageconfig.mdx (1)

26-42: LGTM!

Also applies to: 54-86

Comment on lines +30 to +51
class _BackpressuredStreamWriter:
def __init__(self) -> None:
self.parts: list[bytes] = []
self.drain_calls = 0
self.first_drain_started = asyncio.Event()
self.release_first_drain = asyncio.Event()
self.closed = False

def write(self, data: bytes) -> None:
self.parts.append(data)

async def drain(self) -> None:
self.drain_calls += 1
if self.drain_calls == 1:
self.first_drain_started.set()
await self.release_first_drain.wait()

def close(self) -> None:
self.closed = True

async def wait_closed(self) -> None:
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer AsyncMock/MagicMock doubles over hand-written stub classes.

_BackpressuredStreamWriter (lines 30-51), CancellingWriter (lines 237-251), and FailingWriter (lines 283-288) each define a new class to stand in for a mocked collaborator. The test path instructions require unittest.mock.MagicMock or AsyncMock with spec for this, and require the mock prefix for mocked class names. _BackpressuredStreamWriter needs real coordination state, so an AsyncMock(spec=asyncio.StreamWriter) with side_effect functions for drain keeps the behavior and satisfies the rule. CancellingWriter and FailingWriter map directly onto AsyncMock with side_effect.

As per path instructions: "When mocking a class, use unittest.mock.MagicMock or AsyncMock, using the spec argument when necessary, rather than defining a new class" and "Prefix mocked class names with mock, not fake."

🤖 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 `@tests/adapters/test_adapters_common_lifecycle.py` around lines 30 - 51,
Replace the hand-written _BackpressuredStreamWriter, CancellingWriter, and
FailingWriter doubles with unittest.mock AsyncMock/MagicMock instances, using
spec=asyncio.StreamWriter where appropriate. Preserve backpressure coordination
through the backpressured mock’s drain side_effect and retain the cancellation
and failure behavior through side_effect callbacks, naming any mock variables
with the mock prefix.

Source: Path instructions

Comment on lines +791 to +796
manager = await session_module.SessionManager.create(
config=config,
shared_builder=shared_builder,
)
assert manager.is_workflow_per_user is True
assert manager.get_workflow_streaming_output_schema() is None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm SessionManager exposes is_workflow_per_user and get_workflow_streaming_output_schema.
set -uo pipefail

repo="NVIDIA/NeMo-Agent-Toolkit"
for tag in v1.8.0 develop; do
  echo "== $tag =="
  curl -fsSL "https://raw.githubusercontent.com/$repo/$tag/packages/nvidia_nat_core/src/nat/runtime/session.py" 2>/dev/null \
    | rg -n 'is_workflow_per_user|get_workflow_streaming_output_schema|def create'
done

Repository: NVIDIA/NeMo-Fabric

Length of output: 1661


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "== Repository NAT references =="
rg -n -i 'nemo.?agent|nat|importorskip|SessionManager|is_workflow_per_user|get_workflow_streaming_output_schema' \
  tests/adapters/test_external_nat_adapter.py pyproject.toml python 2>/dev/null | head -300

echo "== NAT API history across release tags =="
repo="NVIDIA/NeMo-Agent-Toolkit"
for tag in v1.0.0 v1.1.0 v1.2.0 v1.3.0 v1.4.0 v1.5.0 v1.6.0 v1.7.0 v1.8.0; do
  url="https://raw.githubusercontent.com/$repo/$tag/packages/nvidia_nat_core/src/nat/runtime/session.py"
  body="$(curl -fsSL "$url" 2>/dev/null || true)"
  if [ -z "$body" ]; then
    echo "$tag: unavailable"
  else
    printf '%s: ' "$tag"
    python3 -c 'import sys; s=sys.stdin.read(); print("is_workflow_per_user=" + str("def is_workflow_per_user" in s) + ", get_workflow_streaming_output_schema=" + str("def get_workflow_streaming_output_schema" in s) + ", create=" + str("async def create" in s))' <<<"$body"
  fi
done

Repository: NVIDIA/NeMo-Fabric

Length of output: 31930


🏁 Script executed:

#!/bin/bash
set -uo pipefail

echo "== Test and adapter context =="
sed -n '700,810p' tests/adapters/test_external_nat_adapter.py
printf '\n== NAT-related project metadata ==\n'
rg -n -i 'nemo.?agent|nat|adapter-tests|harness' \
  pyproject.toml external tests README.md docs 2>/dev/null | head -250

echo "== NAT v1.5.0 package metadata and API declarations =="
for path in \
  packages/nvidia_nat_core/pyproject.toml \
  packages/nvidia_nat_core/src/nat/runtime/session.py \
  packages/nvidia_nat_core/src/nat/builder/per_user_workflow_builder.py; do
  echo "--- $path ---"
  curl -fsSL "https://raw.githubusercontent.com/NVIDIA/NeMo-Agent-Toolkit/v1.5.0/$path" 2>/dev/null \
    | rg -n 'version|requires-python|def is_workflow_per_user|def get_workflow_streaming_output_schema|class SessionManager|class PerUserWorkflowBuilder' \
    | head -80
done

echo "== Exact API tag/ref availability =="
for tag in v1.4.0 v1.5.0; do
  curl -fsSL "https://api.github.com/repos/NVIDIA/NeMo-Agent-Toolkit/git/ref/tags/$tag" 2>/dev/null \
    | jq -r '[.ref, .object.type, .object.sha] | `@tsv`' || true
done

Repository: NVIDIA/NeMo-Fabric

Length of output: 31610


Declare the required NAT API version or guard unsupported releases. The test skips missing modules but does not check either SessionManager member. NAT v1.5.0 provides both members. Enforce that minimum version or skip releases that lack them instead of raising AttributeError.

🤖 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 `@tests/adapters/test_external_nat_adapter.py` around lines 791 - 796, Update
the test around SessionManager.create to require NAT version 1.5.0 or skip
unsupported releases before accessing is_workflow_per_user and
get_workflow_streaming_output_schema. Preserve the existing missing-module skip
behavior and ensure older versions do not fail with AttributeError.

@AjayThorve

Copy link
Copy Markdown
Collaborator Author

Superseded by #208. The replacement contains only the FABRIC-162 native streaming contract plus the schema-gated NAT integration, with all #200 per-user and registry-expansion changes removed.

@AjayThorve AjayThorve closed this Aug 11, 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.

1 participant