Skip to content

fix: do not forward the unknown-model sentinel as a per-request model override - #2141

Merged
rapids-bot[bot] merged 4 commits into
NVIDIA:developfrom
knQzx:fix/react-agent-unknown-model
Aug 11, 2026
Merged

rapids-bot[bot] merged 4 commits into
NVIDIA:developfrom
knQzx:fix/react-agent-unknown-model

Conversation

@knQzx

@knQzx knQzx commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

Closes #2110

when a model can't be resolved, the ReAct agent builder receives the sentinel model="unknown-model" and forwards it straight through as a per-request configurable.model_name override, so every downstream LLM/NIM call is told to use a model literally named unknown-model and the provider returns 404. this is a regression from #2036, which introduced the sentinel but didn't guard against forwarding it

the fix treats the unknown-model sentinel the same as an unset model - the per-request model override is only applied when a real model name is present

the guard sits inside _build_lc_config rather than at its call sites, so it also covers register_per_user_agent.py, which imports the same helper and passes message.model the same way - per_user_react_agent has the identical defect on develop

the sentinel value itself now lives in nat/data_models/api_server.py as UNKNOWN_MODEL_SENTINEL and every reference points at it (the five defaults/fallbacks there, the _string_to_nat_chat_request converter, this guard, and the literal in tool_calling_agent/register.py), so the value can't drift apart between packages

one behaviour note: with the sentinel short-circuiting the block it no longer reaches the supports_override check, so a backend without per-request override support accepts plain string input instead of raising. that is intended - the user never asked for a model - and the test pins it

reproduce: build a ReAct agent whose model resolves to the unknown-model sentinel (an unresolved model reference), then run it. on develop the request carries configurable: {model_name: "unknown-model"} and the call 404s; with this change no model override is attached and the configured default is used. the added test_build_lc_config_ignores_unknown_model_sentinel asserts the sentinel is not forwarded

By Submitting this PR I confirm:

  • I am familiar with the Contributing Guidelines
  • We require that all contributors "sign-off" on their commits - this commit is signed off
  • New tests cover these changes
  • The documentation is up to date with these changes

Summary by CodeRabbit

  • Bug Fixes
    • Prevented unspecified or unknown model values from being incorrectly applied as per-request model overrides.
    • Preserved validation and support checks when a valid model override is provided.
    • Standardized handling of unknown model values across chat responses and streaming responses.
  • Tests
    • Added regression coverage for missing, unknown, and valid model overrides.

@knQzx
knQzx requested a review from a team as a code owner July 28, 2026 10:35
@copy-pr-bot

copy-pr-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The API server centralizes the unknown-model sentinel. The React agent excludes unspecified models from LangChain overrides. Regression tests cover unspecified and explicit model values.

Changes

Model override handling

Layer / File(s) Summary
Centralize unspecified model values
packages/nvidia_nat_core/src/nat/data_models/api_server.py, packages/nvidia_nat_langchain/src/nat/plugins/langchain/agent/tool_calling_agent/register.py
The API server defines UNKNOWN_MODEL_SENTINEL and uses it for chat responses, streaming chunks, and string-to-request conversion. Tool-call streaming chunks use the shared constant.
Filter unspecified model overrides
packages/nvidia_nat_langchain/src/nat/plugins/langchain/agent/react_agent/register.py, packages/nvidia_nat_langchain/tests/agent/test_react.py
_build_lc_config ignores the sentinel and None. Explicit model names still populate the LangChain configurable override. Regression tests cover these cases.

Estimated code review effort: 2 (Simple) | ~10 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Title check ❌ Error The title clearly describes the fix and uses imperative wording, but it is 78 characters and exceeds the approximate 72-character limit. Shorten the title to approximately 72 characters or fewer while preserving its description of the sentinel override fix.
✅ Passed checks (4 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 1

🧹 Nitpick comments (2)
packages/nvidia_nat_langchain/src/nat/plugins/langchain/agent/react_agent/register.py (1)

46-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the sentinel comment and definition with the upstream contract.

ChatRequest.model defaults to None; only the plain-string converter injects "unknown-model". This comment is misleading, and the sentinel literal is duplicated across packages. Prefer a shared constant or update the comment to document the converter behavior.

🤖 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
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/agent/react_agent/register.py`
around lines 46 - 48, The _UNKNOWN_MODEL_SENTINEL definition and comment should
reflect the upstream contract: ChatRequest.model defaults to None, while only
the plain-string converter supplies "unknown-model". Reuse the existing shared
sentinel constant if available; otherwise update the comment to describe the
converter behavior without implying ChatRequest.model uses it by default.
packages/nvidia_nat_langchain/tests/agent/test_react.py (1)

1809-1811: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the unsupported-backend sentinel path.

Add an assertion using supports_override=False; this ensures the sentinel is ignored before the backend capability check and can never raise an unsupported-override error.

Proposed test addition
     assert "configurable" not in _build_lc_config(5, sentinel_model)
+    assert "configurable" not in _build_lc_config(5, sentinel_model, supports_override=False)
     assert "configurable" not in _build_lc_config(5, None)
🤖 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 `@packages/nvidia_nat_langchain/tests/agent/test_react.py` around lines 1809 -
1811, Extend the sentinel-model assertions in the test covering _build_lc_config
to pass supports_override=False, verifying the sentinel is ignored before
backend capability validation and does not raise an unsupported-override error.
Keep the existing assertions for the default behavior and None model unchanged.
🤖 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 `@packages/nvidia_nat_langchain/tests/agent/test_react.py`:
- Around line 1802-1803: Update the regression test docstring in the test
covering the `unknown-model` sentinel so its opening descriptive sentence is
concise, ends with a period on the same line, and uses backticks around code
entities such as `unknown-model` and `ChatRequest`.

---

Nitpick comments:
In
`@packages/nvidia_nat_langchain/src/nat/plugins/langchain/agent/react_agent/register.py`:
- Around line 46-48: The _UNKNOWN_MODEL_SENTINEL definition and comment should
reflect the upstream contract: ChatRequest.model defaults to None, while only
the plain-string converter supplies "unknown-model". Reuse the existing shared
sentinel constant if available; otherwise update the comment to describe the
converter behavior without implying ChatRequest.model uses it by default.

In `@packages/nvidia_nat_langchain/tests/agent/test_react.py`:
- Around line 1809-1811: Extend the sentinel-model assertions in the test
covering _build_lc_config to pass supports_override=False, verifying the
sentinel is ignored before backend capability validation and does not raise an
unsupported-override error. Keep the existing assertions for the default
behavior and None model unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 696a05f2-208f-4ea5-a669-d496745ff658

📥 Commits

Reviewing files that changed from the base of the PR and between baf355b and f74768e.

📒 Files selected for processing (2)
  • packages/nvidia_nat_langchain/src/nat/plugins/langchain/agent/react_agent/register.py
  • packages/nvidia_nat_langchain/tests/agent/test_react.py

…request override

Signed-off-by: knQzx <75641500+knQzx@users.noreply.github.com>
@knQzx
knQzx force-pushed the fix/react-agent-unknown-model branch from f74768e to b23575c Compare July 28, 2026 12:00
@willkill07 willkill07 added bug Something isn't working non-breaking Non-breaking change labels Jul 29, 2026
@chuenchen309

Copy link
Copy Markdown

I verified this independently against develop (2618705). The bug is real and the fix is in the right place; two notes below, one of which I think is worth acting on.

The chain, link by link. _string_to_nat_chat_request is registered on GlobalTypeConverter and hardcodes the sentinel:

def _string_to_nat_chat_request(data: str) -> ChatRequest:
    return ChatRequest.from_string(data, model="unknown-model")   # api_server.py:954-955

_response_fn converts through exactly that path (register.py:169) and hands message.model to _build_lc_config (register.py:184). The remaining question was whether the sentinel survives validation, and it does — _MODEL_NAME_RE accepts it:

>>> _MODEL_NAME_RE.match("unknown-model")
<re.Match object; span=(0, 13), match='unknown-model'>

So on develop, any plain-string input to a ReAct agent attaches configurable: {"model_name": "unknown-model"}. Confirmed.

Fixing inside _build_lc_config rather than at the call sites matters more than it looks. There is a third caller outside this file: register_per_user_agent.py:89 imports _build_lc_config and passes message.model the same way. That import and call arrived in #2036 alongside the sentinel, so per_user_react_agent has the identical defect. A guard placed at the two call sites in register.py would have left it broken; this patch covers all three. Might be worth a line in the description, since the diff touching only react_agent/register.py makes it look narrower than it is.

The one thing I'd change: the sentinel now has two independent definitions. _UNKNOWN_MODEL_SENTINEL is declared in react_agent/register.py, but the value originates in api_server.py, where it appears as a bare literal in six places (lines 375, 401, 430, 455, 489, 955). If any of those is ever changed, this guard keeps comparing against the old string and silently stops matching — the failure mode is the original 404, with no test catching it, because the test derives its input from the converter and would follow the new value.

This repo already has the pattern for the fix: STDOUT_ENDPOINT_SENTINEL in nat/utils/telemetry/config.py:82, referenced from handler.py:230. Exporting something like UNKNOWN_MODEL_SENTINEL from api_server.py and importing it here would make the coupling explicit and let the six literals collapse onto it later.

Minor, and I think intentional: with the sentinel short-circuiting the whole block, it no longer reaches the supports_override check either. That is the correct behaviour — the user did not request a model, so raising "the configured inference backend does not support per-request model selection" at them would be wrong — but it does mean a backend without override support now silently accepts string input that previously raised. Worth stating explicitly rather than leaving as a side effect of the guard's placement.

AI-assisted review. The code paths above were read on develop at 2618705 and the regex match was executed; I did not build the package or run the test suite, so the pass/fail claims in the PR description are not independently confirmed here.

@willkill07

Copy link
Copy Markdown
Member

/ok to test 7f41f9c

knQzx and others added 2 commits August 11, 2026 00:09
…se it

the sentinel value lived as a bare literal in api_server and in both langchain agents, so a change in one place would silently stop matching in the others

Signed-off-by: knQzx <75641500+knQzx@users.noreply.github.com>
@willkill07

Copy link
Copy Markdown
Member

/ok to test a4d96c7

@willkill07

Copy link
Copy Markdown
Member

/merge

@rapids-bot
rapids-bot Bot merged commit 39a3dbe into NVIDIA:develop Aug 11, 2026
15 checks passed
yifei-gpt pushed a commit to yifei-gpt/NeMo-Agent-Toolkit that referenced this pull request Sep 13, 2026
… override (NVIDIA#2141)

Closes NVIDIA#2110

when a model can't be resolved, the ReAct agent builder receives the sentinel `model="unknown-model"` and forwards it straight through as a per-request `configurable.model_name` override, so every downstream LLM/NIM call is told to use a model literally named `unknown-model` and the provider returns 404. this is a regression from NVIDIA#2036, which introduced the sentinel but didn't guard against forwarding it

the fix treats the `unknown-model` sentinel the same as an unset model - the per-request model override is only applied when a real model name is present

the guard sits inside `_build_lc_config` rather than at its call sites, so it also covers `register_per_user_agent.py`, which imports the same helper and passes `message.model` the same way - `per_user_react_agent` has the identical defect on develop

the sentinel value itself now lives in `nat/data_models/api_server.py` as `UNKNOWN_MODEL_SENTINEL` and every reference points at it (the five defaults/fallbacks there, the `_string_to_nat_chat_request` converter, this guard, and the literal in `tool_calling_agent/register.py`), so the value can't drift apart between packages

one behaviour note: with the sentinel short-circuiting the block it no longer reaches the `supports_override` check, so a backend without per-request override support accepts plain string input instead of raising. that is intended - the user never asked for a model - and the test pins it

reproduce: build a ReAct agent whose model resolves to the `unknown-model` sentinel (an unresolved model reference), then run it. on develop the request carries `configurable: {model_name: "unknown-model"}` and the call 404s; with this change no model override is attached and the configured default is used. the added `test_build_lc_config_ignores_unknown_model_sentinel` asserts the sentinel is not forwarded

## By Submitting this PR I confirm:
- I am familiar with the [Contributing Guidelines](https://github.com/NVIDIA/NeMo-Agent-Toolkit/blob/develop/docs/source/resources/contributing/index.md)
- We require that all contributors "sign-off" on their commits - this commit is signed off
- New tests cover these changes
- The documentation is up to date with these changes


## Summary by CodeRabbit

- **Bug Fixes**
  - Prevented unspecified or unknown model values from being incorrectly applied as per-request model overrides.
  - Preserved validation and support checks when a valid model override is provided.
  - Standardized handling of unknown model values across chat responses and streaming responses.
- **Tests**
  - Added regression coverage for missing, unknown, and valid model overrides.

Authors:
  - Novikov Kirill (https://github.com/knQzx)
  - Will Killian (https://github.com/willkill07)

Approvers:
  - Will Killian (https://github.com/willkill07)

URL: NVIDIA#2141
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nat run with react_agent sends the ChatRequest sentinel "unknown-model" as a per-request model override, breaking every NIM call

3 participants