Skip to content

Conversation

@willkill07
Copy link
Member

@willkill07 willkill07 commented Aug 18, 2025

Description

GPT-5 based models don't (currently) support temperature or top_p, which is problematic given the defaulted nature of them within the model. To this end, we should dynamically validate based on the model whether or not to include them in the configuration. This has been accomplished through a new base Mixin for configurations to inherit from.

Note

We are not using the latest version of LangGraph/LangChain which potentially offers better parameter support for gpt-5 based models. Some prompts may need to be updated to better support GPT-5. This PR does make it so it will work and run without encountering any errors.

Summary

  • Introduces ModelGatedFieldMixin to gate config fields based on model support.
  • Adds TemperatureMixin and TopPMixin to provider configs; rejects unsupported usage for GPT-5 models.
  • Updates OpenAI, NIM, and AWS Bedrock configs to include these mixins and defaults.

Why

  • Centralize model-aware validation for config fields.
  • Provide sensible defaults and stricter validation to prevent invalid parameters per model.

Key Changes

  • New
    • src/nat/data_models/model_gated_field_mixin.py: Generic mixin to gate fields by model patterns; supports custom model_keys.
    • src/nat/data_models/temperature_mixin.py: temperature in [0, 1], default 0.0 when supported; unsupported on models matching gpt5.
    • src/nat/data_models/top_p_mixin.py: top_p in [0, 1], default 1.0 when supported; unsupported on models matching gpt5.
    • Tests for all above mixins:
      • tests/nat/data_models/test_model_gated_field_mixin.py
      • tests/nat/data_models/test_temperature_mixin.py
      • tests/nat/data_models/test_top_p_mixin.py
  • Modified
    • src/nat/llm/openai_llm.py: OpenAIModelConfig now includes TemperatureMixin and TopPMixin.
    • src/nat/llm/nim_llm.py: NIMModelConfig now includes TemperatureMixin and TopPMixin.
    • src/nat/llm/aws_bedrock_llm.py: AWSBedrockModelConfig now includes TemperatureMixin.
    • src/nat/llm/azure_openai_llm.py: AzureOpenAIModelConfig now includes TemperatureMixin and TopPMixin.

Behavior Details

  • Model-gated validation
    • Looks for model identifiers in model_name, model, or azure_deployment (overridable via model_keys).
    • If a field is unsupported for the detected model and a value is provided, validation raises an error.
    • If supported and value is omitted, applies default_if_supported.
    • If no model key is present, defaults are applied.
  • Defaults and ranges
    • temperature: default 0.0 when supported; must be within [0, 1].
    • top_p: default 1.0 when supported; must be within [0, 1].

Breaking/Impact

  • Potential validation failures if callers pass temperature or top_p when using GPT-5 models (match on gpt5, case-insensitive).
  • Otherwise backward-compatible; new fields are optional and defaulted when supported.

Migration Guide

  • For GPT-5 models:
    • Do not pass temperature or top_p in configs; remove them from workflows and environment config where present.
  • For custom models/keys:
    • If your config uses a different model identifier field, subclass with model_keys=("<your_key>",).

Tests

  • Coverage for:
    • Selector enforcement (supported_models vs unsupported_models).
    • Default application, range validation, and unsupported-field rejections.
    • Custom model_keys behavior.
  • All new tests live under tests/nat/data_models/.

Checklist

  • Unit tests added/updated
  • Provider configs updated (OpenAI, NIM, AWS Bedrock)
  • Validation and defaults verified
  • Documentation: add a short note on model-gated fields (follow-up PR if needed)

Documentation

What it is

  • A generic mixin for Pydantic models that gates a field based on whether a target model supports it.
  • Provides automatic validation and sensible defaults without duplicating logic across providers.

How it works

  • Scans one or more model identifier keys to determine support:
    • Default keys: model_name, model, azure_deployment (overridable via model_keys)
  • Selection modes (exactly one must be provided):
    • unsupported_models: field is supported unless a regex matches
    • supported_models: field is supported only if a regex matches
  • Behavior:
    • Supported + value is None → sets default_if_supported
    • Supported + value is set → keeps user value
    • Unsupported + value is set → raises ValidationError
    • Unsupported + value is None → leaves as None
    • No model keys present → applies default_if_supported

Defining a gated field (example)

import re
from pydantic import BaseModel, Field
from nat.data_models.model_gated_field_mixin import ModelGatedFieldMixin

_SUPPORTED = (re.compile(r"gpt-4", re.IGNORECASE),)

class FrequencyPenaltyMixin(
    BaseModel,
    ModelGatedFieldMixin[float],
    field_name="frequency_penalty",
    default_if_supported=0.0,
    supported_models=_SUPPORTED,  # alternatively: use unsupported_models=...
):
    frequency_penalty: float | None = Field(default=None, ge=0.0, le=2.0)

Overriding model selector keys

class AzureOnlyMixin(
    BaseModel,
    ModelGatedFieldMixin[int],
    field_name="some_param",
    default_if_supported=1,
    unsupported_models=(re.compile(r"gpt5", re.IGNORECASE),),
    model_keys=("azure_deployment",),  # override detection key(s)
):
    some_param: int | None = Field(default=None)
    azure_deployment: str

Using the built-in mixins

  • TemperatureMixin
    • Field: temperature in [0, 1]
    • Default when supported: 0.0
    • Unsupported on models matching gpt5 (case-insensitive)
  • TopPMixin
    • Field: top_p in [0, 1]
    • Default when supported: 1.0
    • Unsupported on models matching gpt5 (case-insensitive)

Example provider config integration:

from pydantic import BaseModel, Field
from nat.data_models.temperature_mixin import TemperatureMixin
from nat.data_models.top_p_mixin import TopPMixin

class MyProviderConfig(BaseModel, TemperatureMixin, TopPMixin):
    model_name: str = Field(...)
    # temperature/top_p now auto-validated and defaulted based on model support

Best practices

  • Use supported_models for allowlists and unsupported_models for denylists; do not set both.
  • Keep regexes specific (anchor with ^ and $ when possible) to avoid accidental matches.
  • If your config uses a non-standard model identifier, set model_keys accordingly.

Closes #610

By Submitting this PR I confirm:

  • I am familiar with the Contributing Guidelines.
  • We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license.
    • Any contribution which contains commits that are not Signed-Off will not be accepted.
  • When the PR is ready for review, new or existing tests cover these changes.
  • When the PR is ready for review, the documentation is up to date with these changes.

Summary by CodeRabbit

  • New Features

    • Model‑gated configuration fields with automatic defaults and validation.
    • Temperature and top_p now available via reusable mixins; gating enforces model support.
    • ReAct agent adjusts LLM binding/stop behavior for GPT‑5–like models.
  • Refactor

    • Provider configs now compose Temperature/TopP mixins instead of direct fields; minor provider signature cleanups.
  • Documentation

    • New mixins and model‑gated fields docs; provider docs and navigation updated with gating notes.
  • Tests

    • Added tests for gating, temperature, and top_p behaviors.
  • Chores

    • Vocabulary updated to accept “denylist” and pluralized “mixins.”

@willkill07 willkill07 changed the title Wkk gpt5 support Improvement: GPT5 Support Aug 18, 2025
@willkill07 willkill07 added improvement Improvement to existing functionality non-breaking Non-breaking change labels Aug 18, 2025
Signed-off-by: Will Killian <[email protected]>
@willkill07 willkill07 changed the title Improvement: GPT5 Support Improvement: GPT-5 Support Aug 18, 2025
@willkill07 willkill07 added feature request New feature or request and removed improvement Improvement to existing functionality labels Aug 18, 2025
@willkill07 willkill07 changed the title Improvement: GPT-5 Support Feature: GPT-5 Support Aug 18, 2025
Signed-off-by: Will Killian <[email protected]>
Signed-off-by: Will Killian <[email protected]>
@willkill07 willkill07 self-assigned this Aug 19, 2025
Copy link
Contributor

@ericevans-nv ericevans-nv left a comment

Choose a reason for hiding this comment

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

ReAct agents fail with GPT-5 models because llm.bind(stop=["Observation:"]) in https://github.com/NVIDIA/NeMo-Agent-Toolkit/blob/develop/src/nat/agent/react_agent/agent.py#L100 adds runtime stop sequences that GPT-5’s API rejects. We need to detect GPT-5 models and gate this behavior accordingly.

Signed-off-by: Will Killian <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Warning

Rate limit exceeded

@willkill07 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 14 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 1f6f86a and 5c46486.

📒 Files selected for processing (6)
  • docs/source/extend/model-gated-fields.md (1 hunks)
  • src/nat/agent/react_agent/agent.py (2 hunks)
  • src/nat/data_models/temperature_mixin.py (1 hunks)
  • src/nat/data_models/top_p_mixin.py (1 hunks)
  • tests/nat/data_models/test_temperature_mixin.py (1 hunks)
  • tests/nat/data_models/test_top_p_mixin.py (1 hunks)

Walkthrough

Adds model-gated configuration mixins (ModelGatedFieldMixin, TemperatureMixin, TopPMixin), integrates them into LLM provider configs, adjusts ReAct agent LLM binding to avoid stop sequences for GPT‑5‑like models, adds docs and tests for gating/mixins, and minor CI vocabulary regex tweaks.

Changes

Cohort / File(s) Summary
Docs: mixins and gating
docs/source/extend/adding-an-llm-provider.md, docs/source/extend/model-gated-fields.md, docs/source/index.md, docs/source/workflows/llms/index.md
New and updated documentation: explains mixin composition, ModelGatedFieldMixin behavior and examples; updates TOC and provider docs to note temperature/top_p may be model-gated.
Core mixins: gating + fields
src/nat/data_models/model_gated_field_mixin.py, src/nat/data_models/temperature_mixin.py, src/nat/data_models/top_p_mixin.py
Add generic ModelGatedFieldMixin[T] with after-validation gating and runtime helpers; add TemperatureMixin and TopPMixin (defaults, range validation, GPT‑5 exclusions).
Providers adopt mixins
src/nat/llm/openai_llm.py, src/nat/llm/azure_openai_llm.py, src/nat/llm/nim_llm.py, src/nat/llm/aws_bedrock_llm.py
Provider config classes now inherit TemperatureMixin/TopPMixin (as applicable); removed inline temperature/top_p fields; minor provider param rename (builder_builder) where unused.
Agent LLM binding
src/nat/agent/react_agent/agent.py
Add _maybe_bind_llm_and_yield helper; detect GPT‑5-like models and avoid binding stop=["Observation:"] for those models; agent binding adjusted to use helper.
Tests for gating/mixins
tests/nat/data_models/test_model_gated_field_mixin.py, tests/nat/data_models/test_temperature_mixin.py, tests/nat/data_models/test_top_p_mixin.py
New unit tests covering selector validation, defaults, unsupported-model errors, custom model_keys, and numeric range checks.
CI vocabulary
ci/vale/styles/config/vocabularies/nat/accept.txt
Add [Dd]enylist; change [Mm]ixin[Mm]ixin(s?) to accept plural/singular forms and capitalization variants.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User Config
  participant C as ProviderConfig (with Mixins)
  participant M as ModelGatedFieldMixin
  Note over C,M: Pydantic validation (after)
  U->>C: Instantiate with model fields + temperature/top_p
  C->>M: model_validate(after)
  M->>M: Detect model via keys (model_name/model/azure_deployment or custom)
  alt Field supported
    alt Value provided
      M-->>C: Accept provided value
    else No value
      M-->>C: Set default_if_supported (e.g., temp=0.0, top_p=1.0)
    end
  else Field unsupported
    alt Value provided
      M-->>C: Raise ValidationError
    else No value
      M-->>C: Keep None
    end
  end
  C-->>U: Validated config
Loading
sequenceDiagram
  autonumber
  participant App as App
  participant A as ReActAgentGraph
  participant L as LLM
  participant P as Prompt
  App->>A: __init__(llm, prompt)
  A->>A: _maybe_bind_llm_and_yield()
  A->>L: inspect model/model_name
  alt Not GPT‑5-like
    A->>L: bind(stop=["Observation:"])
    A->>A: agent = prompt | bound_llm
  else GPT‑5-like
    A->>A: agent = prompt | llm (no stop)
  end
  A-->>App: Initialized agent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
Omit stop sequence for GPT‑5-like models (#610)
Disallow or gate temperature for GPT‑5 (#610)
Ensure validation errors when unsupported fields are provided (#610)

Assessment — Out-of-scope changes

Code Change Explanation
CI vocabulary regex edits (ci/vale/styles/config/vocabularies/nat/accept.txt) Documentation/CI vocabulary change unrelated to GPT‑5 gating or runtime behavior requested in the linked issue; purely cosmetic/lexical acceptance additions.
Documentation additions (docs/.../*.md) Extensive docs added for mixins and gated fields; these are informational and not required to satisfy the functional objectives of enabling GPT‑5 support.
Parameter rename to _builder (e.g., src/nat/llm/*_llm.py) Renaming unused function parameters to _builder is stylistic/cleanup and does not affect the GPT‑5 support behavior required by the issue.

Poem

I twitch my ears at models five,
No stop, I hop—yet still I thrive.
Temperature? I’ll leave it be,
Unless your model sets it free.
With gated fields and tidy prose,
I bound through configs—on I goes! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
docs/source/extend/adding-an-llm-provider.md (1)

33-36: Typo: “retreiver” → “retriever”

Minor spelling error in a user-facing doc.

-In NeMo Agent toolkit, there are three provider types: `llm`, `embedder`, and `retreiver`.
+In NeMo Agent toolkit, there are three provider types: `llm`, `embedder`, and `retriever`.
src/nat/data_models/model_gated_field_mixin.py (1)

80-126: Critical: Refactor ModelGatedFieldMixin to avoid cross-mixin collisions and remove unnecessary getattr calls

The current implementation of ModelGatedFieldMixin in src/nat/data_models/model_gated_field_mixin.py still:

  • Uses getattr(cls, "...", None) to read constant class attributes (unsupported_models, supported_models, model_keys, default_if_supported)—triggers Ruff B009 and obscures intent.
  • Defines model_validate so that at runtime self.__class__ (“provider” class) is used to look up field_name and helpers, causing:
    • In a provider composing TemperatureMixin + TopPMixin, only one mixin’s validator runs (the one earliest in the MRO), leaving the other field ungated.
    • Mis-reported errors and wrong defaults when both mixins are present.

Mandatory fixes (in src/nat/data_models/model_gated_field_mixin.py, lines 80–126):

  • Replace all getattr(cls, "...", None) with direct cls.<attribute>.
  • In detect_support, skip None or empty model identifiers before checking.
  • In the @model_validator method, remove klass = self.__class__ and use the mixin class captured in the __init_subclass__ closure (cls) so each mixin’s validator always refers to its own field_name, model_keys, etc.

Apply this diff:

@@ class ModelGatedFieldMixin.__init_subclass__:
-    @classmethod
-    def check_model(cls, model_name: str) -> bool:
+    @classmethod
+    def check_model(cls, model_name: str) -> bool:
         """
@@
-        unsupported = getattr(cls, "unsupported_models", None)
-        supported = getattr(cls, "supported_models", None)
+        unsupported = cls.unsupported_models
+        supported   = cls.supported_models
         if unsupported is not None:
             return not any(p.search(model_name) for p in unsupported)
         if supported is not None:
             return any(p.search(model_name) for p in supported)
         return False

@@ class ModelGatedFieldMixin.__init_subclass__:
-    @classmethod
-    def detect_support(cls, instance: BaseModel) -> str | None:
-        for key in getattr(cls, "model_keys"):
-            if hasattr(instance, key):
-                model_name_value = getattr(instance, key)
-                is_supported = getattr(cls, "_model_gated_field_check_model")(str(model_name_value))
-                return key if not is_supported else None
-        return None
+    @classmethod
+    def detect_support(cls, instance: BaseModel) -> str | None:
+        for key in cls.model_keys:
+            if not hasattr(instance, key):
+                continue
+            value = getattr(instance, key)
+            if value is None:
+                continue
+            s = str(value).strip()
+            if not s:
+                continue
+            if not cls._model_gated_field_check_model(s):
+                return key
+        return None

@@ class ModelGatedFieldMixin.__init_subclass__:
-    @model_validator(mode="after")
-    def model_validate(self):
-        klass = self.__class__
-
-        field_name_local = getattr(klass, "field_name")
-        current_value = getattr(self, field_name_local, None)
-
-        found_key = klass._model_gated_field_detect_support(self)
-        if found_key is not None:
-            if current_value is not None:
-                raise ValueError(
-                    f"{field_name_local} is not supported for {found_key}: {getattr(self, found_key)}")
-        elif current_value is None:
-            setattr(self, field_name_local, getattr(klass, "default_if_supported", None))
-        return self
+    @model_validator(mode="after")
+    def model_validate(self):
+        # Use the mixin class (captured as cls in this closure), not the provider,
+        # so each mixin’s own field_name, default_if_supported, etc., are used.
+        field_name_local = cls.field_name      # type: ignore[attr-defined]
+        current_value   = getattr(self, field_name_local, None)
+
+        found_key = cls._model_gated_field_detect_support(self)
+        if found_key is not None:
+            if current_value is not None:
+                raise ValueError(
+                    f"{field_name_local} is not supported for {found_key}: {getattr(self, found_key)}")
+        elif current_value is None:
+            setattr(self, field_name_local, cls.default_if_supported)
+        return self

• Ensure AWSBedrockModelConfig (in src/nat/llm/aws_bedrock_llm.py) includes TopPMixin if top-P gating is required.
• After applying, re-run the existing smoke tests and verify both temperature and top_p are correctly gated for multi-mixin providers.

🧹 Nitpick comments (13)
ci/vale/styles/config/vocabularies/nat/accept.txt (1)

47-47: Broaden “denylist” acceptance pattern to include hyphenated and plural forms
Vale currently only accepts [Dd]enylist, which won’t catch variants like “deny-list” or “denylists” (just as we already accept “API(s?)” and “Callable(s?)”). A quick repo scan showed hyphenated allow-list variants in comments (e.g. extension-pkg-allow-list), so extending the pattern prevents needless Vale warnings and keeps consistency.

• File: ci/vale/styles/config/vocabularies/nat/accept.txt (around line 47)
Apply:

-[Dd]enylist
+[Dd]eny-?list(s?)
src/nat/agent/react_agent/agent.py (1)

101-106: Correctly omits stop for GPT-5; add debug + make stop tokens slightly more robust.

Logic is sound: bind stop only when the model supports it. Consider a tiny enhancement for observability and robustness:

  • Log whether a stop sequence was bound (helps diagnose GPT-5 vs non–GPT-5 behavior in prod).
  • Include both "Observation:" and "\nObservation:" as stop sequences to cover formatting variants.

Proposed inline tweak:

-        if ReActAgentGraph._llm_needs_stop_sequence(llm):
-            bound_llm = llm.bind(stop=["Observation:"])
-            self.agent = prompt | bound_llm
-        else:
-            self.agent = prompt | llm
+        needs_stop = ReActAgentGraph._llm_needs_stop_sequence(llm)
+        logger.debug("%s stop binding applied: %s", AGENT_LOG_PREFIX, needs_stop)
+        self.agent = prompt | (llm.bind(stop=["Observation:", "\nObservation:"]) if needs_stop else llm)
docs/source/workflows/llms/index.md (1)

126-129: Azure note should also mention top_p; mirror AWS section with a temperature note

  • If AzureOpenAIModelConfig includes both TemperatureMixin and TopPMixin (per PR summary), this admonition should include both fields to avoid confusion.
  • Also consider adding a similar note under the AWS Bedrock section indicating that temperature is model-gated there (since AWS gets TemperatureMixin only).

Apply this diff to update the Azure note and add an AWS note:

@@
 ### AWS Bedrock
@@
 * `max_retries` - The maximum number of retries for the request
+
+:::{note}
+`temperature` is a model-gated field and may not be supported by all models. If unsupported and explicitly set, validation will fail. See [Model-Gated Fields](../../extend/model-gated-fields.md) for details.
+:::
@@
 ### Azure OpenAI
@@
-:::{note}
-`temperature` is model-gated and may not be supported by all models. See [Model-Gated Fields](../../extend/model-gated-fields.md) for details.
-:::
+:::{note}
+`temperature` and `top_p` are model-gated fields and may not be supported by all models. If unsupported and explicitly set, validation will fail. See [Model-Gated Fields](../../extend/model-gated-fields.md) for details.
+:::

Please confirm:

  • AzureOpenAIModelConfig actually composes both TemperatureMixin and TopPMixin.
  • AWSBedrockModelConfig composes TemperatureMixin only.
    If not, we should align the docs with the actual compositions.
docs/source/extend/adding-an-llm-provider.md (1)

83-93: Model-Gated Fields: tighten phrasing on rejections

The guidance is solid. Minor suggestion: explicitly state that allowlists (supported_models) treat unknown models as unsupported, while denylists (unsupported_models) treat unknown models as supported. This mirrors the actual behavior and avoids ambiguity.

- Some configuration parameters are only valid for certain models. The toolkit provides built-in mixins that automatically validate and default these parameters based on the selected model.
+ Some configuration parameters are only valid for certain models. The toolkit provides built-in mixins that automatically validate and default these parameters based on the selected model. Note: when using `supported_models` (allowlist), unknown models are treated as unsupported; when using `unsupported_models` (denylist), unknown models are treated as supported.
docs/source/extend/model-gated-fields.md (3)

57-67: Regex example: prefer hyphenated GPT-5 and ignore-case

Deployment/model identifiers typically include the hyphen. Recommend anchoring the example to ^gpt-5$ and retaining re.IGNORECASE (already present).

-    unsupported_models=(re.compile(r"^gpt5$", re.IGNORECASE),),
+    unsupported_models=(re.compile(r"^gpt-5$", re.IGNORECASE),),

71-80: Bullet formatting/wording nit

Minor polish for consistency and readability.

-- {py:class}`~nat.data_models.temperature_mixin.TemperatureMixin`
-  - Field: `temperature` in [0, 1]
-  - Default when supported: `0.0`
-  - Not supported on GPT-5 models
+- {py:class}`~nat.data_models.temperature_mixin.TemperatureMixin`
+  - Field: `temperature` in [0, 1]
+  - Default when supported: `0.0`
+  - Not supported on GPT-5 models.
@@
-- {py:class}`~nat.data_models.top_p_mixin.TopPMixin`
-  - Field: `top_p` in [0, 1]
-  - Default when supported: `1.0`
-  - Not supported on GPT-5 models
+- {py:class}`~nat.data_models.top_p_mixin.TopPMixin`
+  - Field: `top_p` in [0, 1]
+  - Default when supported: `1.0`
+  - Not supported on GPT-5 models.

29-34: Edge case note: empty/None model identifiers

Consider adding a brief note that if a detection key exists but is empty/None, the mixin treats it as “no detectable model” and applies the default (assuming you adopt the code change below to skip empty values). This clarifies behavior for partially populated configs.

tests/nat/data_models/test_model_gated_field_mixin.py (2)

108-122: Edge case: treat None/empty model key as “not present” to avoid surprising gating

Current detect_support() (in ModelGatedFieldMixin) checks hasattr(instance, key) and then stringifies the value. If the attribute exists but is None or "", it will be treated as a present key with a value that almost certainly won’t match any pattern, which flips to “supported” in unsupported_models mode or “unsupported” in supported_models mode unexpectedly. This can lead to defaults being applied or withheld unintentionally when users explicitly pass model_name=None.

Recommend skipping keys whose value is None or "" so behavior matches the “no model key → default_if_supported” rule.

Potential fix in src/nat/data_models/model_gated_field_mixin.py (outside this file), within detect_support():

-            def detect_support(cls, instance: BaseModel) -> str | None:
-                for key in getattr(cls, "model_keys"):
-                    if hasattr(instance, key):
-                        model_name_value = getattr(instance, key)
-                        is_supported = getattr(cls, "_model_gated_field_check_model")(str(model_name_value))
-                        return key if not is_supported else None
+            def detect_support(cls, instance: BaseModel) -> str | None:
+                for key in getattr(cls, "model_keys"):
+                    if hasattr(instance, key):
+                        model_name_value = getattr(instance, key)
+                        # Treat None/empty as absent to follow “no model key present” semantics
+                        if model_name_value in (None, ""):
+                            continue
+                        is_supported = getattr(cls, "_model_gated_field_check_model")(str(model_name_value))
+                        return key if not is_supported else None
                 return None

If you’d like, I can also add a unit test for the None/empty case once the behavior is agreed.


16-24: Add targeted tests for key precedence and empty model_keys validation

To harden the contract, consider:

  • A test proving precedence when multiple keys are present (e.g., model_name vs model vs azure_deployment).
  • A test that model_keys=() raises ValueError (there is a guard, but no test).

Apply this patch to extend coverage:

+def test_model_gated_field_key_precedence():
+
+    class BothKeys(
+            BaseModel,
+            ModelGatedFieldMixin[int],
+            field_name="dummy",
+            default_if_supported=11,
+            # Only 'beta' is supported
+            supported_models=(re.compile(r"^beta$"), ),
+    ):
+        dummy: int | None = Field(default=None)
+        # Both present; by default, model_name is checked before model
+        model_name: str = "alpha"
+        model: str = "beta"
+
+    # Precedence should pick model_name first → unsupported → no default
+    m = BothKeys()
+    assert m.dummy is None
+
+
+def test_model_gated_field_rejects_empty_model_keys_tuple():
+    with pytest.raises(ValueError, match=r"model_keys must be provided and non-empty"):
+        class BadModelKeys(
+                BaseModel,
+                ModelGatedFieldMixin[int],
+                field_name="dummy",
+                default_if_supported=1,
+                supported_models=(re.compile(r".+"), ),
+                model_keys=(),
+        ):
+            dummy: int | None = Field(default=None)
src/nat/llm/aws_bedrock_llm.py (1)

55-58: Param rename to _builder is a clean way to denote unused parameter

No functional impact; avoids linter noise. Also, minor copy edit suggestion in the description string.

Apply this tiny edit for grammar:

-    yield LLMProviderInfo(config=llm_config, description="A AWS Bedrock model for use with an LLM client.")
+    yield LLMProviderInfo(config=llm_config, description="An AWS Bedrock model for use with an LLM client.")

Confirm that AWS Bedrock intentionally omits TopPMixin. If bedrock models accept a top_p/topP parameter, consider adding TopPMixin here (and mapping to provider-specific naming if needed). I can prep a follow-up patch if desired.

src/nat/data_models/temperature_mixin.py (1)

26-36: Multiple BaseModel mixins: acceptable, but consider simplifying later

TemperatureMixin also subclasses BaseModel. Pydantic v2 tolerates multiple BaseModel bases, but a leaner approach is to have mixins avoid inheriting BaseModel and rely on the concrete config class to provide it. That can reduce MRO complexity and accidental model_config resolution surprises.

If you decide to refactor later, consider:

  • Make TemperatureMixin a plain mixin (object), and keep the validator registration in ModelGatedFieldMixin working on BaseModel subclasses via typing Protocols or relaxed annotations.
  • Centralize model patterns in a small constants module to avoid repetition across mixins.
src/nat/llm/openai_llm.py (1)

29-29: Config now inherits gating mixins: LGTM, but unify max_retries ownership

Adopting TemperatureMixin/TopPMixin here is correct. One consistency nit: this class still declares max_retries (Line 40) while Azure and NIM rely on RetryMixin. Keeping the field in only one place avoids divergence.

Proposed cleanup (remove the explicit field from this class and rely on RetryMixin’s default):

 class OpenAIModelConfig(LLMBaseConfig, RetryMixin, TemperatureMixin, TopPMixin, name="openai"):
@@
-    max_retries: int = Field(default=10, description="The max number of retries for the request.")

If RetryMixin doesn’t declare max_retries yet, consider moving the shared definition there for all providers.

src/nat/llm/azure_openai_llm.py (1)

29-49: Consider adding an Azure-specific test to exercise azure_deployment detection

Unit tests cover the mixins, but an end-to-end Azure model config case (e.g., azure_deployment="gpt-5o") would guard against regressions in alias handling.

I can add a small test under tests/nat/llm/ to instantiate AzureOpenAIModelConfig with azure_deployment="gpt-5o" and assert temperature/top_p remain None while validation passes. Want me to draft it?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9e1de and 4504237.

📒 Files selected for processing (16)
  • ci/vale/styles/config/vocabularies/nat/accept.txt (2 hunks)
  • docs/source/extend/adding-an-llm-provider.md (1 hunks)
  • docs/source/extend/model-gated-fields.md (1 hunks)
  • docs/source/index.md (1 hunks)
  • docs/source/workflows/llms/index.md (3 hunks)
  • src/nat/agent/react_agent/agent.py (2 hunks)
  • src/nat/data_models/model_gated_field_mixin.py (1 hunks)
  • src/nat/data_models/temperature_mixin.py (1 hunks)
  • src/nat/data_models/top_p_mixin.py (1 hunks)
  • src/nat/llm/aws_bedrock_llm.py (2 hunks)
  • src/nat/llm/azure_openai_llm.py (1 hunks)
  • src/nat/llm/nim_llm.py (2 hunks)
  • src/nat/llm/openai_llm.py (2 hunks)
  • tests/nat/data_models/test_model_gated_field_mixin.py (1 hunks)
  • tests/nat/data_models/test_temperature_mixin.py (1 hunks)
  • tests/nat/data_models/test_top_p_mixin.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
tests/nat/data_models/test_top_p_mixin.py (1)
src/nat/data_models/top_p_mixin.py (1)
  • TopPMixin (26-36)
tests/nat/data_models/test_model_gated_field_mixin.py (1)
src/nat/data_models/model_gated_field_mixin.py (1)
  • ModelGatedFieldMixin (27-125)
src/nat/llm/aws_bedrock_llm.py (1)
src/nat/data_models/temperature_mixin.py (1)
  • TemperatureMixin (26-36)
tests/nat/data_models/test_temperature_mixin.py (1)
src/nat/data_models/temperature_mixin.py (1)
  • TemperatureMixin (26-36)
src/nat/data_models/top_p_mixin.py (1)
src/nat/data_models/model_gated_field_mixin.py (1)
  • ModelGatedFieldMixin (27-125)
src/nat/llm/nim_llm.py (2)
src/nat/data_models/temperature_mixin.py (1)
  • TemperatureMixin (26-36)
src/nat/data_models/top_p_mixin.py (1)
  • TopPMixin (26-36)
src/nat/llm/openai_llm.py (2)
src/nat/data_models/temperature_mixin.py (1)
  • TemperatureMixin (26-36)
src/nat/data_models/top_p_mixin.py (1)
  • TopPMixin (26-36)
src/nat/data_models/temperature_mixin.py (1)
src/nat/data_models/model_gated_field_mixin.py (1)
  • ModelGatedFieldMixin (27-125)
src/nat/llm/azure_openai_llm.py (2)
src/nat/data_models/temperature_mixin.py (1)
  • TemperatureMixin (26-36)
src/nat/data_models/top_p_mixin.py (1)
  • TopPMixin (26-36)
🪛 LanguageTool
docs/source/extend/adding-an-llm-provider.md

[grammar] ~87-~87: There might be a mistake here.
Context: ..., with a default of 0.0 when supported - TopPMixin: adds a top_p field in [0, 1], with a...

(QB_NEW_EN)

docs/source/extend/model-gated-fields.md

[grammar] ~71-~71: There might be a mistake here.
Context: ... ## Built-in Gated Mixins - {py:class}~nat.data_models.temperature_mixin.TemperatureMixin - Field: temperature in [0, 1] - Defau...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...ixin - Field:temperaturein [0, 1] - Default when supported:0.0` - Not su...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...in [0, 1] - Default when supported:0.0` - Not supported on GPT-5 models - {py:cla...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ... supported on GPT-5 models - {py:class}~nat.data_models.top_p_mixin.TopPMixin - Field: top_p in [0, 1] - Default whe...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ....TopPMixin - Field:top_pin [0, 1] - Default when supported:1.0` - Not su...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...in [0, 1] - Default when supported:1.0` - Not supported on GPT-5 models ### Examp...

(QB_NEW_EN)

🪛 Ruff (0.12.2)
src/nat/data_models/model_gated_field_mixin.py

100-100: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)


103-103: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)


113-113: Do not call getattr with a constant attribute value. It is not any safer than normal property access.

Replace getattr with attribute access

(B009)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CI Pipeline / Check
🔇 Additional comments (39)
ci/vale/styles/config/vocabularies/nat/accept.txt (1)

81-81: LGTM: plural “Mixins” accepted.

The updated pattern [Mm]ixin(s?) aligns with existing pluralization patterns in this file and will quiet Vale across docs mentioning multiple Mixins.

src/nat/agent/react_agent/agent.py (2)

19-19: Import is appropriate.

The added re import is needed for the new model-detection helper.


109-116: Update GPT-5 regex to cover “gpt-5o” variants

The proposed pattern r"\bgpt-?5(?:\b|[-._])" still fails to detect variants like gpt-5o (e.g. gpt-5o-2025-06-18, azure/prod-gpt-5o), causing needs_stop=True when it should be False.

• Location: src/nat/agent/react_agent/agent.py, lines 109–116
• Tests show:

  • openai_gpt5o → needs_stop should be False but is True
  • azure_prefixed → needs_stop should be False but is True

Suggested diff to catch any alphanumeric or separator suffix after “gpt-5”:

     @staticmethod
     def _llm_needs_stop_sequence(llm: BaseChatModel) -> bool:
-        pattern = re.compile(r"\bgpt-?5(?:\b|[-._])", re.IGNORECASE)
+        # match any GPT-5 variant, including letter- or digit-suffixes (e.g. gpt-5o, gpt-5-32k)
+        pattern = re.compile(r"\bgpt-?5[\w\.-]*", re.IGNORECASE)
         model_id_attrs = ("model", "model_name", "azure_deployment")
         identifiers = (getattr(llm, attr, "") or "" for attr in model_id_attrs)
         haystack = " ".join(s for s in identifiers if isinstance(s, str))
         return not bool(pattern.search(haystack))

This revised regex (\bgpt-?5[\w\.-]*) ensures all GPT-5 forks—including gpt-5o, gpt5, gpt-5.1, prod-gpt-5-32k—are properly detected and won’t have a stop sequence bound.

Likely an incorrect or invalid review comment.

docs/source/index.md (1)

116-116: Nav entry for Model-Gated Fields — looks good

The new toctree item is placed logically alongside “Adding an LLM Provider” under Extend. No issues.

docs/source/workflows/llms/index.md (2)

69-72: Good callout: model-gated fields for NIM

Clear, concise note linking to the detailed docs. LGTM.


91-94: Good callout: model-gated fields for OpenAI

Consistent wording with the NIM section; link path is correct.

docs/source/extend/adding-an-llm-provider.md (4)

59-66: Mixins section is a helpful addition

Clear explanation of why mixins exist and how they augment provider configs. Good separation of concerns.


67-81: RetryMixin example: consistent and minimal

Example correctly demonstrates composition and extra="allow". No issues.


94-115: TemperatureMixin example: OK

Accurately shows composition and field semantics. No action needed.


116-134: TopPMixin example: OK

Consistent with TemperatureMixin example. No action needed.

docs/source/extend/model-gated-fields.md (1)

18-34: Great overview and behavior table

The detection keys, selector modes, and behavior matrix are crisp and actionable. Nice work.

tests/nat/data_models/test_model_gated_field_mixin.py (7)

26-43: LGTM: selector mutual exclusivity is enforced at class definition time

Both “both-provided” and “neither-provided” paths are correctly guarded with clear error messages. Good use of class-definition-time validation for fast feedback.


44-58: LGTM: selector presence validation

The error path for missing selectors is covered and messages are precise. This prevents silent misconfiguration of the mixin.


60-74: LGTM: default application when supported and value is None

Behavior matches the documented contract: supported + omitted → default applied.


76-90: LGTM: validation error when unsupported and value is set

The raised ValidationError and message format match the mixin’s validator. Good.


92-106: LGTM: unsupported + omitted returns None

This correctly preserves “unsupported + omitted → leave None”.


124-141: LGTM: custom model_keys (unsupported path) behaves as intended

Gating on a non-default key and leaving the field as None when banned is correct and readable.


142-158: LGTM: custom model_keys (supported path) applies default

Completes the supported-path coverage for custom keys.

tests/nat/data_models/test_top_p_mixin.py (5)

22-29: LGTM: default top_p when supported

Defaulting to 1.0 for supported models is asserted correctly.


31-38: LGTM: value retention for supported models

The provided value is preserved as expected.


49-56: LGTM: unsupported + omitted yields None

This aligns with gating semantics.


58-68: LGTM: range validation

Upper and lower bounds are covered.


70-74: LGTM: default when no model keys are present

Direct instantiation confirms fallback to default_if_supported.

tests/nat/data_models/test_temperature_mixin.py (5)

22-29: LGTM: default temperature when supported

Defaulting to 0.0 for supported models is asserted correctly.


31-38: LGTM: value retention for supported models

The provided value is preserved as expected.


49-56: LGTM: unsupported + omitted yields None

Behavior is correct and mirrors top_p.


58-68: LGTM: range validation

Bounds are exercised.


70-74: LGTM: default when no model keys present

Direct instantiation confirms fallback to default_if_supported.

src/nat/llm/aws_bedrock_llm.py (1)

25-29: LGTM: TemperatureMixin integration

Adopting TemperatureMixin for AWSBedrockModelConfig brings parity with other providers and centralizes validation/defaults.

src/nat/data_models/top_p_mixin.py (1)

26-36: LGTM: mixin definition and field constraints

The field bounds and docstring are appropriate; integration with ModelGatedFieldMixin is clean.

src/nat/data_models/temperature_mixin.py (1)

1-15: License header: LGTM

SPDX header and licensing block are correct and consistent with the repo.

src/nat/llm/openai_llm.py (3)

25-26: Importing TemperatureMixin and TopPMixin: LGTM

Good modularization; keeps provider configs thin and model-aware.


44-44: Rename builder → _builder: LGTM

Eliminates the unused-arg warning without behavioral changes.


29-47: Verify gating in external LLM‐wrapper integration

I didn’t find any code in src/nat/llm/openai_llm.py (or its builder support) that actually assembles the HTTP payload—request construction appears to live in the LangChain (or other) wrapper you plug in at runtime. Because of that:

  • There’s no direct payload["temperature"] = … or dict‐comprehension filtering in this repo.
  • Neither OpenAIModelConfig nor AzureOpenAIModelConfig contains logic to omit nulls—they simply define the fields.
  • The gating must therefore happen in your LLM‐framework integration (e.g. LangChain’s OpenAI/ChatOpenAI class).

Please manually verify in your wrapper code (the part that maps OpenAIModelConfig → LangChain → HTTP request) that temperature and top_p are only added when not None—otherwise a JSON null may be sent downstream.

src/nat/llm/nim_llm.py (3)

26-27: Importing TemperatureMixin and TopPMixin: LGTM

Consistent with OpenAI/Azure providers.


30-30: Config now inherits gating mixins: LGTM

Defaulting and gating will apply uniformly for NIM configs too.


44-44: Rename builder → _builder: LGTM

No functional change, resolves unused parameter.

src/nat/llm/azure_openai_llm.py (2)

25-26: Importing TemperatureMixin and TopPMixin: LGTM

Matches the shared mixin strategy used across providers.


29-29: Config now inherits gating mixins: LGTM

Gating keys include azure_deployment by default, so detection works for Azure-specific model IDs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4504237 and a24c82c.

📒 Files selected for processing (1)
  • src/nat/agent/react_agent/agent.py (2 hunks)
🔇 Additional comments (2)
src/nat/agent/react_agent/agent.py (2)

19-19: Imports look good and are necessary for the new typing and regex logic.
No issues spotted.

Also applies to: 27-27, 34-34


103-103: Nice refactor: delegate LLM binding to a helper.
This keeps init clean and expresses intent clearly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/nat/agent/react_agent/agent.py (1)

116-122: Fix None-handling in GPT‑5 detection and include azure_deployment (repeat).

  • If self.llm.model or self.llm.model_name exists but is None, re.Pattern.search(None) raises a TypeError at runtime.
  • Azure deployments identify models via azure_deployment; align with the PR’s model-key defaults to avoid missing GPT‑5 on Azure.
  • Keep using search() (good) to match substrings like openai/gpt-5o.

Apply this diff:

-        # models that don't need (or don't support)a stop sequence
-        smart_models = re.compile(r"gpt-?5", re.IGNORECASE)
-        if any(smart_models.search(getattr(self.llm, model, "")) for model in ["model", "model_name"]):
+        # models that don't need (or don't support) a stop sequence
+        gpt5_re = re.compile(r"gpt-?5", re.IGNORECASE)
+        model_attrs = ("model", "model_name", "azure_deployment")
+        identifiers: list[str] = []
+        for attr in model_attrs:
+            val = getattr(self.llm, attr, None)
+            if val:
+                identifiers.append(str(val))
+        if any(gpt5_re.search(identifier) for identifier in identifiers):
             # no need to bind any additional parameters to the LLM
             return self.llm

If you adopt the module-level constant from my earlier comment, replace gpt5_re with GPT5_REGEX and drop the in-method compile.

Run this to confirm attribute coverage across providers and catch other model-id properties you may want to include:

#!/bin/bash
set -euo pipefail

echo "Scanning provider LLMs for model identifier attributes…"
rg -nP -C2 '\b(model_name|azure_deployment|model_id|model)\b' src/nat/llm 2>/dev/null || true

echo
echo "Searching for azure_deployment usage outside llm modules…"
rg -nP -C2 '\bazure_deployment\b' src 2>/dev/null || true
🧹 Nitpick comments (6)
src/nat/agent/react_agent/agent.py (2)

19-19: Hoist GPT‑5 regex to a module-level constant for reuse.

Compiling the same regex inside the method is unnecessary. Define it once at module scope and reuse to keep the hot path cheap and the intent clear.

Add near the imports (outside the changed hunk):

# Models that don't need (or don't support) a stop sequence (e.g., gpt-5, gpt5, gpt-5o, etc.)
GPT5_REGEX = re.compile(r"gpt-?5", re.IGNORECASE)

107-115: Docstring nit: clarify phrasing.

Minor wording/grammar nits. Consider: “If the LLM is a GPT‑5 variant, return it unmodified; otherwise bind a stop sequence.”

docs/source/extend/model-gated-fields.md (4)

29-34: Clarify precedence and fallback behavior when model keys are missing or conflicting

Two edge cases would benefit from explicit wording:

  • If multiple detection keys are present (e.g., both model_name and azure_deployment), which one is used and in what order?
  • “No detection keys present → applies default_if_supported” can surprise users on providers where a field is actually unsupported. Consider a note that consumers who can’t surface a model key should prefer supported_models (allowlist) to avoid false-positive defaults, or set model_keys=() to disable auto-defaulting.

Would you like me to open a small docs PR to add a short “Precedence and fallbacks” callout box?


69-80: Tighten wording and add explicit GPT‑5 regex to avoid ambiguity

  • Minor grammar/punctuation: end each bullet with a period for consistency.
  • Consider adding the actual GPT‑5 detection pattern used by the built-in mixins (e.g., r"gpt-?5") to set expectations about future variants (gpt5, gpt-5, gpt-5.1, etc.).

Suggested text tweak:

 - {py:class}`~nat.data_models.temperature_mixin.TemperatureMixin`
-  - Field: `temperature` in [0, 1]
-  - Default when supported: `0.0`
-  - Not supported on GPT-5 models
+  - Field: `temperature` in [0, 1].
+  - Default when supported: `0.0`.
+  - Not supported on GPT‑5 models (matched via regex like `r"gpt-?5"`).
 
 - {py:class}`~nat.data_models.top_p_mixin.TopPMixin`
-  - Field: `top_p` in [0, 1]
-  - Default when supported: `1.0`
-  - Not supported on GPT-5 models
+  - Field: `top_p` in [0, 1].
+  - Default when supported: `1.0`.
+  - Not supported on GPT‑5 models (matched via regex like `r"gpt-?5"`).

Can you confirm the exact regex used in the mixins so the doc reflects the implementation precisely?


81-91: Consider adding a short “provider-specific notes” callout

Since different providers wire in different subsets (e.g., AWSBedrockModelConfig includes TemperatureMixin only per the PR), a one-line note with links to each provider config would help users know what’s available where.

I can add a small table linking provider configs to the gated fields they expose (temperature, top_p), plus any provider-specific model_keys defaults. Want me to draft that?


95-97: Unify with earlier statement by making the “exactly one” constraint explicit here too

This repeats guidance from above but without “exactly one” phrasing. Suggest aligning the wording and mentioning that the mixin enforces this at validation time with a clear error.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a24c82c and 1f6f86a.

📒 Files selected for processing (5)
  • docs/source/extend/model-gated-fields.md (1 hunks)
  • src/nat/agent/react_agent/agent.py (2 hunks)
  • src/nat/data_models/top_p_mixin.py (1 hunks)
  • tests/nat/data_models/test_temperature_mixin.py (1 hunks)
  • tests/nat/data_models/test_top_p_mixin.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/nat/data_models/top_p_mixin.py
  • tests/nat/data_models/test_top_p_mixin.py
  • tests/nat/data_models/test_temperature_mixin.py
🧰 Additional context used
🪛 LanguageTool
docs/source/extend/model-gated-fields.md

[grammar] ~71-~71: There might be a mistake here.
Context: ... ## Built-in Gated Mixins - {py:class}~nat.data_models.temperature_mixin.TemperatureMixin - Field: temperature in [0, 1] - Defau...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...ixin - Field:temperaturein [0, 1] - Default when supported:0.0` - Not su...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...in [0, 1] - Default when supported:0.0` - Not supported on GPT-5 models - {py:cla...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ... supported on GPT-5 models - {py:class}~nat.data_models.top_p_mixin.TopPMixin - Field: top_p in [0, 1] - Default whe...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ....TopPMixin - Field:top_pin [0, 1] - Default when supported:1.0` - Not su...

(QB_NEW_EN)


[grammar] ~78-~78: There might be a mistake here.
Context: ...in [0, 1] - Default when supported:1.0` - Not supported on GPT-5 models ### Examp...

(QB_NEW_EN)

🔇 Additional comments (4)
src/nat/agent/react_agent/agent.py (2)

27-35: LGTM on new type imports.

LanguageModelInput and Runnable are correctly used for the method’s return type and composition; imports look appropriate for langchain-core.


103-103: Nice composition change.

Delegating the bind decision to a helper keeps __init__ clean and preserves the prompt | llm pipeline ergonomics.

docs/source/extend/model-gated-fields.md (2)

22-28: Concept and options are clear and consistent with PR goals

The explanation of detection keys and the “exactly one of supported_models/unsupported_models” requirement is crisp and aligns with the migration behavior described in the PR summary.


20-21: Nice use of MyST roles for API cross-references

The {py:class} references look correct and should resolve in the built docs.

@willkill07
Copy link
Member Author

@ericevans-nv

I wanted to confirm that this is working so I made the following change:

diff --git a/examples/object_store/user_report/configs/config_s3.yml b/examples/object_store/user_report/configs/config_s3.yml
index 3040d4ca..d81a1e32 100644
--- a/examples/object_store/user_report/configs/config_s3.yml
+++ b/examples/object_store/user_report/configs/config_s3.yml
@@ -29,10 +29,9 @@ object_stores:
     bucket_name: my-bucket
 
 llms:
-  nim_llm:
-    _type: nim
-    model_name: meta/llama-3.1-70b-instruct
-    temperature: 0.0
+  llm:
+    _type: openai
+    model_name: gpt-5
 
 functions:
   get_user_report:
@@ -80,7 +79,7 @@ functions:
 workflow:
   _type: react_agent
   tool_names: [get_user_report, put_user_report, update_user_report, delete_user_report]
-  llm_name: nim_llm
+  llm_name: llm
   verbose: true
   handle_parsing_errors: true
   max_retries: 2

And ran this example:

$ nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input 'Get the latest report for user 6789'

And got the following (mostly expected) output:

Configuration Summary:
--------------------
Workflow Type: react_agent
Number of Functions: 4
Number of LLMs: 1
Number of Embedders: 0
Number of Memory: 0
Number of Object Stores: 1
Number of Retrievers: 0
Number of TTC Strategies: 0
Number of Authentication Providers: 0

2025-08-22 16:09:41,844 - nat.agent.react_agent.agent - INFO -
------------------------------
[AGENT]
Agent input: Get the latest report for user 6789
Agent's thoughts:
Question: Get the latest report for user 6789
Thought: I should fetch the latest report using get_user_report
Action: get_user_report
Action Input: {"user_id":"6789","date":null}
Observation: wait for the human to respond with the result from the tool, do not assume the response
------------------------------
2025-08-22 16:09:41,852 - nat_user_report.user_report_tools - INFO - Fetching report from reports/6789/latest.json
2025-08-22 16:09:41,865 - nat.builder.function - ERROR - Error with ainvoke in function with input: {'user_id': '6789', 'date': None}.
Traceback (most recent call last):
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/packages/nvidia_nat_s3/src/nat/plugins/s3/s3_object_store.py", line 144, in get_object
    response = await self._client.get_object(Bucket=self.bucket_name, Key=key)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/.venv/lib/python3.12/site-packages/aiobotocore/context.py", line 36, in wrapper
    return await func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/.venv/lib/python3.12/site-packages/aiobotocore/client.py", line 424, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.NoSuchKey: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/src/nat/builder/function.py", line 149, in ainvoke
    result = await self._ainvoke(converted_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/src/nat/builder/function.py", line 325, in _ainvoke
    return await self._ainvoke_fn(value)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/src/nat/builder/function_info.py", line 466, in _convert_input_pydantic
    return await saved_final_single_fn(**value.model_dump())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/examples/object_store/user_report/src/nat_user_report/user_report_tools.py", line 42, in _inner
    item = await object_store.get_object(key=key)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/packages/nvidia_nat_s3/src/nat/plugins/s3/s3_object_store.py", line 149, in get_object
    raise NoSuchKeyError(key=key, additional_message=str(e))
nat.data_models.object_store.NoSuchKeyError: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
2025-08-22 16:09:41,873 - nat.agent.base - ERROR - [AGENT] Tool call failed after all retry attempts. Last error: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
2025-08-22 16:09:41,873 - nat.agent.base - INFO -
------------------------------
[AGENT]
Calling tools: get_user_report
Tool's input: {'user_id': '6789', 'date': None}
Tool's response:
Tool call failed after all retry attempts. Last error: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
------------------------------
2025-08-22 16:09:41,873 - nat.agent.base - INFO -
------------------------------
[AGENT]
Calling tools: get_user_report
Tool's input: {"user_id":"6789","date":null}
Tool's response:
Tool call failed after all retry attempts. Last error: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
------------------------------
2025-08-22 16:09:50,076 - nat.agent.react_agent.agent - INFO -
------------------------------
[AGENT]
Agent input: Get the latest report for user 6789
Agent's thoughts:
Thought: I now know the final answer
Final Answer: There is no “latest” report for user 6789. How would you like to proceed?
- I can fetch a report for a specific date (YYYY-MM-DD) if you provide one.
- I can create a new “latest” report—send me the report content and I’ll save it.
- Or I can create/update a report for a specific date—provide the date and content.
------------------------------
2025-08-22 16:09:50,081 - nat.front_ends.console.console_front_end_plugin - INFO -
--------------------------------------------------
Workflow Result:
['There is no “latest” report for user 6789. How would you like to proceed?\n- I can fetch a report for a specific date (YYYY-MM-DD) if you provide one.\n- I can create a new “latest” report—send me the report content and I’ll save it.\n- Or I can create/update a report for a specific date—provide the date and content.']
--------------------------------------------------

1 similar comment
@willkill07
Copy link
Member Author

@ericevans-nv

I wanted to confirm that this is working so I made the following change:

diff --git a/examples/object_store/user_report/configs/config_s3.yml b/examples/object_store/user_report/configs/config_s3.yml
index 3040d4ca..d81a1e32 100644
--- a/examples/object_store/user_report/configs/config_s3.yml
+++ b/examples/object_store/user_report/configs/config_s3.yml
@@ -29,10 +29,9 @@ object_stores:
     bucket_name: my-bucket
 
 llms:
-  nim_llm:
-    _type: nim
-    model_name: meta/llama-3.1-70b-instruct
-    temperature: 0.0
+  llm:
+    _type: openai
+    model_name: gpt-5
 
 functions:
   get_user_report:
@@ -80,7 +79,7 @@ functions:
 workflow:
   _type: react_agent
   tool_names: [get_user_report, put_user_report, update_user_report, delete_user_report]
-  llm_name: nim_llm
+  llm_name: llm
   verbose: true
   handle_parsing_errors: true
   max_retries: 2

And ran this example:

$ nat run --config_file examples/object_store/user_report/configs/config_s3.yml --input 'Get the latest report for user 6789'

And got the following (mostly expected) output:

Configuration Summary:
--------------------
Workflow Type: react_agent
Number of Functions: 4
Number of LLMs: 1
Number of Embedders: 0
Number of Memory: 0
Number of Object Stores: 1
Number of Retrievers: 0
Number of TTC Strategies: 0
Number of Authentication Providers: 0

2025-08-22 16:09:41,844 - nat.agent.react_agent.agent - INFO -
------------------------------
[AGENT]
Agent input: Get the latest report for user 6789
Agent's thoughts:
Question: Get the latest report for user 6789
Thought: I should fetch the latest report using get_user_report
Action: get_user_report
Action Input: {"user_id":"6789","date":null}
Observation: wait for the human to respond with the result from the tool, do not assume the response
------------------------------
2025-08-22 16:09:41,852 - nat_user_report.user_report_tools - INFO - Fetching report from reports/6789/latest.json
2025-08-22 16:09:41,865 - nat.builder.function - ERROR - Error with ainvoke in function with input: {'user_id': '6789', 'date': None}.
Traceback (most recent call last):
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/packages/nvidia_nat_s3/src/nat/plugins/s3/s3_object_store.py", line 144, in get_object
    response = await self._client.get_object(Bucket=self.bucket_name, Key=key)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/.venv/lib/python3.12/site-packages/aiobotocore/context.py", line 36, in wrapper
    return await func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/.venv/lib/python3.12/site-packages/aiobotocore/client.py", line 424, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.NoSuchKey: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/src/nat/builder/function.py", line 149, in ainvoke
    result = await self._ainvoke(converted_input)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/src/nat/builder/function.py", line 325, in _ainvoke
    return await self._ainvoke_fn(value)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/src/nat/builder/function_info.py", line 466, in _convert_input_pydantic
    return await saved_final_single_fn(**value.model_dump())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/examples/object_store/user_report/src/nat_user_report/user_report_tools.py", line 42, in _inner
    item = await object_store.get_object(key=key)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/wkillian/Desktop/NeMo-Agent-Toolkit/packages/nvidia_nat_s3/src/nat/plugins/s3/s3_object_store.py", line 149, in get_object
    raise NoSuchKeyError(key=key, additional_message=str(e))
nat.data_models.object_store.NoSuchKeyError: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
2025-08-22 16:09:41,873 - nat.agent.base - ERROR - [AGENT] Tool call failed after all retry attempts. Last error: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
2025-08-22 16:09:41,873 - nat.agent.base - INFO -
------------------------------
[AGENT]
Calling tools: get_user_report
Tool's input: {'user_id': '6789', 'date': None}
Tool's response:
Tool call failed after all retry attempts. Last error: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
------------------------------
2025-08-22 16:09:41,873 - nat.agent.base - INFO -
------------------------------
[AGENT]
Calling tools: get_user_report
Tool's input: {"user_id":"6789","date":null}
Tool's response:
Tool call failed after all retry attempts. Last error: No object found with key: reports/6789/latest.json. An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.
------------------------------
2025-08-22 16:09:50,076 - nat.agent.react_agent.agent - INFO -
------------------------------
[AGENT]
Agent input: Get the latest report for user 6789
Agent's thoughts:
Thought: I now know the final answer
Final Answer: There is no “latest” report for user 6789. How would you like to proceed?
- I can fetch a report for a specific date (YYYY-MM-DD) if you provide one.
- I can create a new “latest” report—send me the report content and I’ll save it.
- Or I can create/update a report for a specific date—provide the date and content.
------------------------------
2025-08-22 16:09:50,081 - nat.front_ends.console.console_front_end_plugin - INFO -
--------------------------------------------------
Workflow Result:
['There is no “latest” report for user 6789. How would you like to proceed?\n- I can fetch a report for a specific date (YYYY-MM-DD) if you provide one.\n- I can create a new “latest” report—send me the report content and I’ll save it.\n- Or I can create/update a report for a specific date—provide the date and content.']
--------------------------------------------------

@willkill07
Copy link
Member Author

/merge

@rapids-bot rapids-bot bot merged commit d031954 into NVIDIA:develop Aug 22, 2025
13 checks passed
@willkill07 willkill07 mentioned this pull request Aug 22, 2025
2 tasks
saglave pushed a commit to snps-scm13/SNPS-NeMo-Agent-Toolkit that referenced this pull request Sep 2, 2025
GPT-5 based models don't (currently) support `temperature` or `top_p`, which is problematic given the defaulted nature of them within the model. To this end, we should dynamically validate based on the model whether or not to include them in the configuration. This has been accomplished through a new base `Mixin` for configurations to inherit from.

> [!NOTE]
> We are not using the latest version of LangGraph/LangChain which potentially offers better parameter support for gpt-5 based models. Some prompts may need to be updated to better support GPT-5. This PR does make it so it will work and run without encountering any errors.

### Summary
- Introduces `ModelGatedFieldMixin` to gate config fields based on model support.
- Adds `TemperatureMixin` and `TopPMixin` to provider configs; rejects unsupported usage for GPT-5 models.
- Updates OpenAI, NIM, and AWS Bedrock configs to include these mixins and defaults.

### Why
- Centralize model-aware validation for config fields.
- Provide sensible defaults and stricter validation to prevent invalid parameters per model.

### Key Changes
- **New**
  - `src/nat/data_models/model_gated_field_mixin.py`: Generic mixin to gate fields by model patterns; supports custom `model_keys`.
  - `src/nat/data_models/temperature_mixin.py`: `temperature` in [0, 1], default 0.0 when supported; unsupported on models matching `gpt5`.
  - `src/nat/data_models/top_p_mixin.py`: `top_p` in [0, 1], default 1.0 when supported; unsupported on models matching `gpt5`.
  - Tests for all above mixins:
    - `tests/nat/data_models/test_model_gated_field_mixin.py`
    - `tests/nat/data_models/test_temperature_mixin.py`
    - `tests/nat/data_models/test_top_p_mixin.py`
- **Modified**
  - `src/nat/llm/openai_llm.py`: `OpenAIModelConfig` now includes `TemperatureMixin` and `TopPMixin`.
  - `src/nat/llm/nim_llm.py`: `NIMModelConfig` now includes `TemperatureMixin` and `TopPMixin`.
  - `src/nat/llm/aws_bedrock_llm.py`: `AWSBedrockModelConfig` now includes `TemperatureMixin`.
  - `src/nat/llm/azure_openai_llm.py`: `AzureOpenAIModelConfig` now includes `TemperatureMixin` and `TopPMixin`.

### Behavior Details
- **Model-gated validation**
  - Looks for model identifiers in `model_name`, `model`, or `azure_deployment` (overridable via `model_keys`).
  - If a field is unsupported for the detected model and a value is provided, validation raises an error.
  - If supported and value is omitted, applies `default_if_supported`.
  - If no model key is present, defaults are applied.
- **Defaults and ranges**
  - `temperature`: default 0.0 when supported; must be within [0, 1].
  - `top_p`: default 1.0 when supported; must be within [0, 1].

### Breaking/Impact
- Potential validation failures if callers pass `temperature` or `top_p` when using GPT-5 models (match on `gpt5`, case-insensitive).
- Otherwise backward-compatible; new fields are optional and defaulted when supported.

### Migration Guide
- For GPT-5 models:
  - Do not pass `temperature` or `top_p` in configs; remove them from workflows and environment config where present.
- For custom models/keys:
  - If your config uses a different model identifier field, subclass with `model_keys=("<your_key>",)`.

### Tests
- Coverage for:
  - Selector enforcement (`supported_models` vs `unsupported_models`).
  - Default application, range validation, and unsupported-field rejections.
  - Custom `model_keys` behavior.
- All new tests live under `tests/nat/data_models/`.

#

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

Approvers:
  - David Gardner (https://github.com/dagardner-nv)
  - Eric Evans II (https://github.com/ericevans-nv)

URL: NVIDIA#664
Signed-off-by: Sangharsh Aglave <[email protected]>
@willkill07 willkill07 deleted the wkk_gpt5-support branch October 23, 2025 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA]: GPT-5 support

3 participants