Skip to content

feat(python): add RunOutput response contract - #52

Merged
rapids-bot[bot] merged 3 commits into
NVIDIA:mainfrom
ericevans-nv:feat/output-response-field
Jul 12, 2026
Merged

feat(python): add RunOutput response contract#52
rapids-bot[bot] merged 3 commits into
NVIDIA:mainfrom
ericevans-nv:feat/output-response-field

Conversation

@ericevans-nv

@ericevans-nv ericevans-nv commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Overview

Adds a Python SDK RunOutput mapping contract so object-shaped RunResult.output
exposes response as a known JSON-valued field while preserving adapter-specific
extension fields and existing mapping access.

Details

  • Adds and exports RunOutput from the Python SDK.
  • Wraps object-shaped RunResult.output values as RunOutput while preserving
    non-object JSON output as-is.
  • Keeps RunOutput.response aligned with the core Fabric JSON contract instead
    of enforcing a string-only adapter convention.
  • Preserves structured output.response values through attribute access,
    mapping access, and to_mapping().
  • Updates the code review example --show-output path to read the normalized
    response value safely.
  • Adds SDK contract coverage for response access, extension preservation,
    explicit null responses, structured response values, and non-object output
    preservation.
  • Regenerates the Python API reference so RunOutput is documented.

Validation

  • git diff --check
  • just --set no_uv true test-python (254 passed, 4 skipped)

Where should the reviewer start?

Start with python/src/nemo_fabric/types.py, specifically the new RunOutput
type and RunResult._normalize() wrapping logic.

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

  • Relates to: FABRIC-63

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.

  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Summary by CodeRabbit

  • New Features
    • Added the public RunOutput type to the Python SDK.
    • Normalized object-shaped adapter outputs into RunOutput, including response plus preserved extension fields.
    • Non-object outputs continue to be preserved unchanged.
  • Documentation
    • Updated API reference docs to include RunOutput and revised RunResult.output documentation.
  • Examples
    • Updated the code review example to read and display response from normalized output when available.
  • Tests
    • Expanded SDK contract tests to validate RunOutput behavior and RunResult wrapping/serialization.

Signed-off-by: Eric Evans <194135482+ericevans-nv@users.noreply.github.com>
@ericevans-nv
ericevans-nv requested a review from a team as a code owner July 10, 2026 19:54
@linear

linear Bot commented Jul 10, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds the public RunOutput model, normalizes mapping-shaped RunResult.output values, preserves non-object values, updates the example consumer, and documents and tests the new behavior.

Changes

RunOutput model and normalization

Layer / File(s) Summary
RunOutput contract and RunResult normalization
python/src/nemo_fabric/types.py, python/src/nemo_fabric/__init__.py, docs/reference/api/python-library-reference/*
Adds validated RunOutput support, exports it publicly, converts mapping outputs during RunResult normalization, preserves non-object values, and updates API documentation.

Consumer integration and contract tests

Layer / File(s) Summary
Consumer integration and contract tests
examples/code_review_agent/__main__.py, tests/python/test_code_review_example.py, tests/python/test_sdk_contract.py
Reads normalized responses through attribute access and verifies mapping conversion, response omission and null handling, serialization, and non-object output preservation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Adapter
  participant RunResult
  participant RunOutput
  participant CodeReviewExample
  Adapter->>RunResult: provide adapter output
  RunResult->>RunOutput: convert mapping-shaped output
  RunOutput-->>RunResult: provide response and extra fields
  RunResult-->>CodeReviewExample: expose normalized output
  CodeReviewExample->>RunOutput: read response
  RunOutput-->>CodeReviewExample: return response or None
Loading

Possibly related PRs

  • NVIDIA/NeMo-Fabric#45: Updates the same code review example’s --show-output response extraction logic.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 follows Conventional Commits and accurately summarizes the RunOutput contract change.
Description check ✅ Passed The description includes the required overview, reviewer start point, related issue, checks, and confirmation items.
✨ 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: 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 `@docs/reference/api/python-library-reference/nemo_fabric.types.md`:
- Around line 858-861: Add a blank line immediately after the generated
RunOutput heading in the RunOutput API reference section, ensuring the heading
is surrounded by blank lines and markdownlint MD022 passes.

In `@tests/python/test_sdk_contract.py`:
- Around line 723-767: Add a test alongside the existing RunOutput tests that
calls RunOutput.from_mapping with a non-string response such as {"response":
123} and asserts it raises FabricConfigError using pytest.raises, covering the
validation branch for invalid response values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: f194558e-9541-4744-8a50-465be16505e1

📥 Commits

Reviewing files that changed from the base of the PR and between fcf49dc and 9508f5e.

📒 Files selected for processing (7)
  • docs/reference/api/python-library-reference/index.md
  • docs/reference/api/python-library-reference/nemo_fabric.types.md
  • examples/code_review_agent/__main__.py
  • python/src/nemo_fabric/__init__.py
  • python/src/nemo_fabric/types.py
  • tests/python/test_code_review_example.py
  • tests/python/test_sdk_contract.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: CodeRabbit / Review
  • GitHub Check: Build and publish docs
  • GitHub Check: Pre-commit
  • GitHub Check: Build wheels (x86_64)
  • GitHub Check: Test (arm64)
  • GitHub Check: Test (x86_64)
🧰 Additional context used
📓 Path-based instructions (6)
**

⚙️ CodeRabbit configuration file

**:

Contributing to NeMo Fabric

Thank you for your interest in contributing to NeMo Fabric. This guide covers
the development workflow, coding standards, and pull request process.

Development Setup

This section collects the setup steps needed before building, testing, or
contributing changes.

Package Installation

NeMo Fabric is not currently available on PyPI. To consume the Python packages,
build wheels from a source checkout:

just wheels
uv pip install --find-links dist "nemo-fabric[runtime]"

Adapters are distributed as optional extras. For example, install the Hermes
SDK adapter with:

uv pip install --find-links dist "nemo-fabric[adapters-hermes-sdk]"

Refer to the installation guide for the
complete list of adapters and installation options.

Source Development

Install these tools before you start:

  • Rust (stable toolchain) -- install with rustup
  • Python >= 3.11
  • uv -- follow the uv installation guide
  • just >= 1.50.0 -- cargo install just --locked

Clone the repository, create a virtual environment, and build the Rust and
Python packages:

git clone https://github.com/NVIDIA/NeMo-Fabric.git
cd NeMo-Fabric

uv venv --seed .venv --python 3.13
source .venv/bin/activate
uv sync --all-groups --all-extras
just no_uv=true build-all

Verify the checkout by running the test suites described in
Testing Requirements.

Release Tagging

Versioned release tags must use raw Rust-compatible SemVer without a leading
v.

  • Use 0.1.0 for stable releases.
  • Use 0.1.0-rc.1 for prereleases.
  • Do not create tags such as v0.1.0 or v0.1.0-rc.1.

This keeps release tags aligned with Cargo package versions and lets...

Files:

  • docs/reference/api/python-library-reference/index.md
  • python/src/nemo_fabric/__init__.py
  • tests/python/test_code_review_example.py
  • examples/code_review_agent/__main__.py
  • tests/python/test_sdk_contract.py
  • python/src/nemo_fabric/types.py
  • docs/reference/api/python-library-reference/nemo_fabric.types.md
{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.

Files:

  • docs/reference/api/python-library-reference/index.md
  • docs/reference/api/python-library-reference/nemo_fabric.types.md
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/__init__.py
  • python/src/nemo_fabric/types.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 test functions; async tests are detected and run automatically.
Do not add -> None return type annotations to test functions.
When mocking a class, do not define a new class; use unittest.mock.MagicMock or unittest.mock.AsyncMock, adding spec when needed.
Name mocked classes with a mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not duplicate fixtures across test files; if a fixture is needed in multiple test files, define it in conftest.py.
When creating a fixture, use @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and define the function as <fixture_name>_fixture() -> <return_type>; only pass scope when it is not function.
Prefer pytest.mark.parametrize over creating separate tests for different input types.
If a fixture is needed for a test but does not return a value, or its value is unused, use @pytest.mark.usefixtures.
When modifying environment variables in a test, use os.environ; tests/conftest.py provides an autouse restore_environ_fixture that restores environment variables after each test, so monkeypatch.setenv is unnecessary.

Files:

  • tests/python/test_code_review_example.py
  • tests/python/test_sdk_contract.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/python/test_code_review_example.py
  • tests/python/test_sdk_contract.py
{adapters/**,examples/**}

⚙️ CodeRabbit configuration file

{adapters/**,examples/**}: Review adapter and example changes for command correctness, config/schema consistency, artifact handling, and compatibility with the public Fabric contracts.

Files:

  • examples/code_review_agent/__main__.py
🧠 Learnings (1)
📚 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/__init__.py
  • python/src/nemo_fabric/types.py
🧬 Code graph analysis (1)
python/src/nemo_fabric/types.py (2)
tests/python/test_sdk_contract.py (1)
  • RunResult (40-40)
python/src/nemo_fabric/__init__.py (1)
  • RunResult (44-44)
🪛 markdownlint-cli2 (0.22.1)
docs/reference/api/python-library-reference/nemo_fabric.types.md

[warning] 858-858: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🪛 Ruff (0.15.20)
python/src/nemo_fabric/types.py

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

(TRY003)

🔇 Additional comments (8)
python/src/nemo_fabric/types.py (2)

1223-1223: 📐 Maintainability & Code Quality

Confirm the TRY003 policy for this exception message.

Ruff reports TRY003 on Line 1223. If this rule is enforced, follow the repository’s exception-message convention or explicitly suppress it consistently for FabricConfigError.

Source: Linters/SAST tools


1208-1222: LGTM!

Also applies to: 1224-1233, 1251-1252, 1269-1269, 1294-1294, 1328-1332

python/src/nemo_fabric/__init__.py (1)

41-41: LGTM!

Also applies to: 75-75

docs/reference/api/python-library-reference/nemo_fabric.types.md (1)

821-852: LGTM!

Also applies to: 864-888, 944-944

docs/reference/api/python-library-reference/index.md (1)

46-46: LGTM!

examples/code_review_agent/__main__.py (1)

57-61: LGTM!

tests/python/test_code_review_example.py (1)

25-25: LGTM!

Also applies to: 126-127

tests/python/test_sdk_contract.py (1)

36-36: LGTM!

Also applies to: 697-720, 770-774

Comment thread docs/reference/api/python-library-reference/nemo_fabric.types.md Outdated
Comment thread tests/python/test_sdk_contract.py
Signed-off-by: Eric Evans <194135482+ericevans-nv@users.noreply.github.com>

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/reference/api/python-library-reference/nemo_fabric.types.md (1)

893-899: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the RunOutput.from_mapping return type concrete.

Line 896 advertises FabricMapping, but the implementation returns cls(mapping), so RunOutput.from_mapping(...) produces a RunOutput. Document the concrete type—or update the source annotation to use Self and regenerate the reference—so typed consumers can discover .response correctly.

🤖 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 `@docs/reference/api/python-library-reference/nemo_fabric.types.md` around
lines 893 - 899, Update the documented return type for `RunOutput.from_mapping`
to the concrete `RunOutput` type, or change the source annotation for
`from_mapping` to use `Self` and regenerate the API reference. Ensure the
generated signature accurately exposes `RunOutput` members such as `.response`.

Source: Path instructions

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

Outside diff comments:
In `@docs/reference/api/python-library-reference/nemo_fabric.types.md`:
- Around line 893-899: Update the documented return type for
`RunOutput.from_mapping` to the concrete `RunOutput` type, or change the source
annotation for `from_mapping` to use `Self` and regenerate the API reference.
Ensure the generated signature accurately exposes `RunOutput` members such as
`.response`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9e345dca-7246-4f35-910d-9292f766201e

📥 Commits

Reviewing files that changed from the base of the PR and between 9508f5e and 4007291.

📒 Files selected for processing (2)
  • docs/reference/api/python-library-reference/nemo_fabric.types.md
  • tests/python/test_sdk_contract.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: CodeRabbit / Review
  • GitHub Check: Build and publish docs
  • GitHub Check: Pre-commit
  • GitHub Check: Test (arm64)
  • GitHub Check: Build wheels (x86_64)
  • GitHub Check: Test (x86_64)
🧰 Additional context used
📓 Path-based instructions (4)
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 test functions; async tests are detected and run automatically.
Do not add -> None return type annotations to test functions.
When mocking a class, do not define a new class; use unittest.mock.MagicMock or unittest.mock.AsyncMock, adding spec when needed.
Name mocked classes with a mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not duplicate fixtures across test files; if a fixture is needed in multiple test files, define it in conftest.py.
When creating a fixture, use @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and define the function as <fixture_name>_fixture() -> <return_type>; only pass scope when it is not function.
Prefer pytest.mark.parametrize over creating separate tests for different input types.
If a fixture is needed for a test but does not return a value, or its value is unused, use @pytest.mark.usefixtures.
When modifying environment variables in a test, use os.environ; tests/conftest.py provides an autouse restore_environ_fixture that restores environment variables after each test, so monkeypatch.setenv is unnecessary.

Files:

  • tests/python/test_sdk_contract.py
**

⚙️ CodeRabbit configuration file

**:

Contributing to NeMo Fabric

Thank you for your interest in contributing to NeMo Fabric. This guide covers
the development workflow, coding standards, and pull request process.

Development Setup

This section collects the setup steps needed before building, testing, or
contributing changes.

Package Installation

NeMo Fabric is not currently available on PyPI. To consume the Python packages,
build wheels from a source checkout:

just wheels
uv pip install --find-links dist "nemo-fabric[runtime]"

Adapters are distributed as optional extras. For example, install the Hermes
SDK adapter with:

uv pip install --find-links dist "nemo-fabric[adapters-hermes-sdk]"

Refer to the installation guide for the
complete list of adapters and installation options.

Source Development

Install these tools before you start:

  • Rust (stable toolchain) -- install with rustup
  • Python >= 3.11
  • uv -- follow the uv installation guide
  • just >= 1.50.0 -- cargo install just --locked

Clone the repository, create a virtual environment, and build the Rust and
Python packages:

git clone https://github.com/NVIDIA/NeMo-Fabric.git
cd NeMo-Fabric

uv venv --seed .venv --python 3.13
source .venv/bin/activate
uv sync --all-groups --all-extras
just no_uv=true build-all

Verify the checkout by running the test suites described in
Testing Requirements.

Release Tagging

Versioned release tags must use raw Rust-compatible SemVer without a leading
v.

  • Use 0.1.0 for stable releases.
  • Use 0.1.0-rc.1 for prereleases.
  • Do not create tags such as v0.1.0 or v0.1.0-rc.1.

This keeps release tags aligned with Cargo package versions and lets...

Files:

  • tests/python/test_sdk_contract.py
  • docs/reference/api/python-library-reference/nemo_fabric.types.md
{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/python/test_sdk_contract.py
{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.

Files:

  • docs/reference/api/python-library-reference/nemo_fabric.types.md
🧬 Code graph analysis (1)
tests/python/test_sdk_contract.py (1)
python/src/nemo_fabric/types.py (1)
  • RunOutput (1209-1232)
🔇 Additional comments (2)
docs/reference/api/python-library-reference/nemo_fabric.types.md (1)

858-889: LGTM!

Also applies to: 904-921, 945-945

tests/python/test_sdk_contract.py (1)

36-36: LGTM!

Also applies to: 718-781

@ericevans-nv ericevans-nv self-assigned this Jul 10, 2026
@ericevans-nv ericevans-nv added the enhancement New feature or request label Jul 10, 2026

@AjayThorve AjayThorve left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Python now rejects valid Fabric output accepted by the core.

RunOutput requires output.response to be str | None, but the authoritative Rust RunResult.output remains an unrestricted serde_json::Value, and the run-result schema imposes no response contract. Consequently, an adapter can return a core-valid result such as:

{"output": {"response": {"text": "hello"}}}

The CLI/core accepts it, while Fabric.run() raises FabricConfigError while decoding the successful result. That introduces cross-surface incompatibility and can turn adapter success into an SDK failure.

Either:

  1. Make response: string | null a real Fabric-wide contract in Rust/schema/adapter validation, with the corresponding compatibility decision; or
  2. Keep RunOutput.response permissive and provide a separate typed accessor for canonical text.

Signed-off-by: Eric Evans <194135482+ericevans-nv@users.noreply.github.com>
@ericevans-nv

Copy link
Copy Markdown
Contributor Author

Python now rejects valid Fabric output accepted by the core.

RunOutput requires output.response to be str | None, but the authoritative Rust RunResult.output remains an unrestricted serde_json::Value, and the run-result schema imposes no response contract. Consequently, an adapter can return a core-valid result such as:

{"output": {"response": {"text": "hello"}}}

The CLI/core accepts it, while Fabric.run() raises FabricConfigError while decoding the successful result. That introduces cross-surface incompatibility and can turn adapter success into an SDK failure.

Either:

  1. Make response: string | null a real Fabric-wide contract in Rust/schema/adapter validation, with the corresponding compatibility decision; or
  2. Keep RunOutput.response permissive and provide a separate typed accessor for canonical text.

Thanks, agreed. I changed this to avoid making Python stricter than the core contract.

RunOutput.response is now permissive and returns the raw JSON value (JSONValue | None) rather than enforcing str | None. So a core-valid result like {"output": {"response": {"text": "hello"}}} now decodes successfully, preserves the structured value through attribute access, mapping access, and to_mapping(), and no longer raises FabricConfigError.

I also added SDK contract coverage for structured output.response values and updated the Python API reference to describe response as a JSON-valued field rather than canonical text.

@AjayThorve
AjayThorve self-requested a review July 10, 2026 22:25
@ericevans-nv

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit c27f32b into NVIDIA:main Jul 12, 2026
11 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 13, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants