Skip to content

Conversation

@willkill07
Copy link
Member

@willkill07 willkill07 commented Aug 20, 2025

Description

Overview

This PR introduces Function Groups, a new feature in the NVIDIA NeMo Agent toolkit that enables bundling related functions together to share configuration, context, and resources. This feature provides better organization, resource efficiency, and more flexible function management within workflows.

Key Features Added

1. Function Group Core Implementation

  • New FunctionGroup class in src/nat/builder/function.py - Manages collections of related functions with shared context
  • Configuration system with FunctionGroupBaseConfig supporting include/exclude lists for selective function exposure
  • Resource sharing - Functions within a group share the same configuration and runtime context (database connections, caches, etc.)

2. Selective Function Exposure

  • Include/exclude lists - Control which functions are globally addressable and wrapped as tools
  • Namespaced addressing - Functions referenced as group_name.function_name (e.g., math.add)
  • Programmatic access - All functions remain accessible through the group, regardless of include/exclude settings

3. Workflow Integration

  • New function_groups configuration section in workflow YAML files
  • Tool wrapping support - Request tools for entire groups or individual functions
  • WorkflowBuilder integration with add_function_group() method

4. Registration and CLI Support

  • New @register_function_group decorator for registering function groups
  • Type registry enhancements to support function group discovery and metadata
  • CLI integration for function group management

Major Changes

Configuration Format Changes

  • Before: Individual functions defined separately in functions section
  • After: Related functions grouped together in function_groups section with shared configuration

Example transformation:

# Before
functions:
  get_user_report:
    _type: get_user_report
    object_store: report_object_store
  put_user_report:
    _type: put_user_report  
    object_store: report_object_store
  update_user_report:
    _type: update_user_report
    object_store: report_object_store
  delete_user_report:
    _type: delete_user_report
    object_store: report_object_store

...    

workflow:
  _type: react_agent
  tool_names: [get_user_report, put_user_report, update_user_report, delete_user_report]
  ...

# After  
function_groups:
  user_report:
    _type: user_report
    object_store: report_object_store
    include: [get, put, update, delete]

...    

workflow:
  _type: react_agent
  tool_names: [user_report]
  ...

GitHub Tools Consolidation

  • Consolidated 7 separate GitHub tool files into a single github_tools.py function group
  • Reduced code duplication and improved maintainability
  • Shared configuration for GitHub API credentials and settings

Files Modified

Core Implementation (18 files):

  • src/nat/builder/function.py - Added FunctionGroup class (+172 lines)
  • src/nat/data_models/function.py - Added FunctionGroupBaseConfig (+34 lines)
  • src/nat/builder/workflow_builder.py - Added function group support (+220 lines)
  • src/nat/cli/register_workflow.py - Added @register_function_group decorator (+43 lines)
  • src/nat/cli/type_registry.py - Added type registry support (+71 lines)

GitHub Tools Refactor (8 files):

  • Added: src/nat/tool/github_tools.py (+450 lines) - Consolidated implementation
  • Removed: 7 individual GitHub tool files (-954 lines total)

Documentation (3 files):

  • Added: docs/source/extend/function-groups.md (+136 lines) - Developer guide
  • Added: docs/source/workflows/function-groups.md (+162 lines) - User guide
  • Updated: docs/source/index.md - Added navigation links

Examples and Tests (13 files):

  • Updated all object store examples to use function groups
  • Added comprehensive test suite (+610 lines in test_builder.py)
  • Updated configuration files across multiple examples

Benefits

  1. Resource Efficiency - Shared connections, caches, and context across related functions
  2. Better Organization - Related functions grouped by domain (GitHub tools, user reports, etc.)
  3. Flexible Access Control - Granular control over function visibility and tool wrapping
  4. Reduced Boilerplate - Shared configuration eliminates repetitive setup code
  5. Backward Compatibility - Existing individual functions continue to work unchanged

Testing

  • 610+ new test lines covering function group functionality
  • Integration tests for workflow builder function group support
  • Configuration validation tests for include/exclude list behavior
  • Tool wrapping tests for various agent frameworks

Breaking Changes

The GitHub tools are now exposed as two function groups.

Documentation

  • Complete developer guide for writing custom function groups
  • User guide for configuring and using function groups in workflows
  • Updated navigation and cross-references in documentation
  • Code examples demonstrating both simple and advanced usage patterns

Commit Summary: 3 commits implementing function groups with include/exclude functionality, GitHub tools consolidation, and comprehensive documentation.


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

    • First‑class Function Groups: register, reference and use grouped tools alongside individual tools across builders, agents, and CLI.
  • Configuration

    • user_report examples consolidated into a single function group; workflows updated to reference the group and include max_retries.
  • Agents

    • Agent/tool-call configs now accept function groups; agent tool configurations gained extra controls (description, max_iterations, system_prompt, return_direct, etc.).
  • Documentation

    • New guides and workflow docs for creating, registering, and using function groups.
  • Tests

    • Expanded coverage for groups: creation, include/exclude semantics, conflicts, dependencies, and workflow integration.
  • Chores

    • GitHub tooling consolidated into two group-based integrations; CI allowlist updated.

@willkill07 willkill07 added this to the Release - 25.10 milestone Aug 20, 2025
@willkill07 willkill07 added feature request New feature or request non-breaking Non-breaking change DO NOT MERGE PR should not be merged; see PR for details labels Aug 20, 2025
@willkill07 willkill07 force-pushed the wkk_function-groups branch 11 times, most recently from 401b77f to 195624c Compare August 26, 2025 19:27
@coderabbitai
Copy link

coderabbitai bot commented Aug 26, 2025

Walkthrough

Adds first-class Function Groups: data models/configs, builder/runtime APIs, CLI/type-registry registration, workflow integration, agent configs accepting group refs, tooling for group-based tool resolution, docs/examples updated, GitHub tools consolidated, tests and mocks extended to exercise groups.

Changes

Cohort / File(s) Summary
Docs & Examples
docs/source/extend/function-groups.md, docs/source/workflows/function-groups.md, docs/source/index.md, examples/object_store/user_report/README.md
New docs for Function Groups; TOC updates; README updated to show function-group naming, YAML and agent/tool usage examples.
Example configs — user_report
examples/object_store/user_report/configs/config_*.yml
Replace four per-operation functions with function_groups.user_report (_type: user_report); rename per-op descriptionget/put/update/delete_description; workflows reference [user_report]; add max_retries: 2.
Example implementation — user_report
examples/object_store/user_report/src/.../user_report_tools.py
Replace multiple @register_function configs with a single @register_function_group and UserReportConfig; build and yield a FunctionGroup exposing get/put/update/delete operations that share object_store.
Builder core & function grouping
src/nat/builder/{builder.py,function.py,component_utils.py,workflow.py,workflow_builder.py}
Add FunctionGroup class and builder surface for add/get function_group(s), config and dependency APIs; process FUNCTION_GROUPS in component order; propagate function_groups into Workflow; extend get_tools to resolve group refs and wrap group functions; mark get_tools abstract.
CLI & TypeRegistry
src/nat/cli/{register_workflow.py,type_registry.py}
Add register_function_group decorator/entrypoint; RegisteredFunctionGroupInfo and group-type aliases; register/lookup/get_registered_function_groups and annotation support for FunctionGroup configs.
Data models & refs
src/nat/data_models/{function.py,component.py,component_ref.py,config.py,function_dependencies.py}
Add FunctionGroupBaseConfig (include/exclude validation); add FUNCTION_GROUP/FUNCTION_GROUPS enum entries; add FunctionGroupRef; add function_groups field to Config with annotations/validation; track function_groups in FunctionDependencies.
Agents — tool typing
src/nat/agent/{react_agent/register.py,rewoo_agent/register.py,tool_calling_agent/register.py}
Import FunctionGroupRef and broaden tool_names types to `FunctionRef
Agent runtime — dependency composition
src/nat/agent/{reasoning_agent/reasoning_agent.py,experimental/.../plan_select_execute_function.py}
When composing used tools, include function-group dependencies by merging results from builder.get_function_group_dependencies(...).
Front-end & profiler
src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py, src/nat/profiler/utils.py
MCP worker surfaces accessible functions from workflow.function_groups into function registry; profiler helper typing accepts RegisteredFunctionGroupInfo.
Tooling — GitHub
src/nat/tool/github_tools.py, src/nat/tool/register.py, removed src/nat/tool/github_tools/*.py
Consolidate multiple GitHub helpers into two function groups (github, github_files_tool) in github_tools.py; remove legacy per-tool modules and update exports to github_tool and github_files_tool.
Tests & Mocks
tests/nat/builder/test_builder.py, tests/nat/front_ends/mcp/test_mcp_custom_routes.py, tests/nat/agent/test_reasoning_agent.py, packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
Add FunctionGroup-focused test registrations and assertions; extend MockBuilder and mocks to support function-group APIs; adjust tests to use set-typed dependency fields and group-dependency flows.
CI & Misc
ci/scripts/path_checks.py
Add "delete/recreate" to allowlisted path-check words.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev
  participant CLI as register_function_group
  participant TR as TypeRegistry
  participant WB as WorkflowBuilder
  participant RG as RegisteredBuildFn
  participant FG as FunctionGroup

  Dev->>CLI: @register_function_group(config_type=...)
  CLI->>TR: register_function_group(RegisteredFunctionGroupInfo)
  TR-->>CLI: OK
  Dev->>WB: add_function_group("user_report", config)
  WB->>RG: invoke build_fn(config, builder)
  RG-->>WB: yields FunctionGroup
  WB->>WB: register exposed functions (instance.function_name)
  WB-->>Dev: FunctionGroup recorded in workflow
Loading
sequenceDiagram
  autonumber
  participant CFG as AgentConfig
  participant AG as Agent
  participant WB as WorkflowBuilder
  participant FG as FunctionGroup

  CFG-->>AG: tool_names = [FunctionGroupRef("user_report"), FunctionRef("tool_x")]
  AG->>WB: get_tools(tool_names, wrapper_type)
  WB->>FG: get_accessible_functions("user_report")
  FG-->>WB: {"user_report.get","user_report.put",...}
  WB->>WB: wrap group functions + individual tools
  WB-->>AG: list of wrapped tools
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.88% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat!: Implement Function Groups" concisely and accurately summarizes the primary change—introducing Function Groups—uses imperative mood, and stays within length limits. The "feat!" prefix and exclamation mark appropriately signal a feature and a breaking change consistent with the PR description. The wording directly reflects the changes in code, configs, docs, and examples.
✨ 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

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

@willkill07 willkill07 force-pushed the wkk_function-groups branch from 195624c to e7b4a0e Compare August 26, 2025 20:16
@willkill07

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@willkill07 willkill07 changed the title DRAFT: Feature: Function Groups feat: Function Groups Aug 26, 2025
coderabbitai[bot]

This comment was marked as outdated.

@willkill07 willkill07 force-pushed the wkk_function-groups branch from e7b4a0e to be40b6c Compare August 27, 2025 14:13
@willkill07

This comment was marked as outdated.

@coderabbitai

This comment was marked as outdated.

@coderabbitai coderabbitai bot added the breaking Breaking change label Aug 27, 2025
coderabbitai[bot]

This comment was marked as outdated.

@willkill07 willkill07 force-pushed the wkk_function-groups branch 2 times, most recently from 7f46af3 to a44e4e9 Compare August 27, 2025 14:48
@willkill07 willkill07 removed the breaking Breaking change label Aug 27, 2025
@willkill07

This comment was marked as outdated.

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

Caution

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

⚠️ Outside diff range comments (2)
examples/object_store/user_report/configs/config_redis.yml (1)

60-66: Outdated function name in docs; use namespaced group function.

Docs reference delete_user_report, which no longer exists with function groups. Use user_report.delete (or the exact namespaced address your runtime resolves).

-    delete_description: >
-      Deletes user diagnostic report from the bucket given a user ID and date. If the report does not exist, it will fail.
-      Do not run `delete_user_report` without the explicit intention to delete an existing report, even in the case of an attempt
-      to insert a new report which already exists.
+    delete_description: |
+      Deletes a user diagnostic report from the bucket given a user ID and date. If the report does not exist, it will fail.
+      Do not call `user_report.delete` unless deletion is explicitly intended; never use it to resolve put/update conflicts.
       Args:
         user_id: str: The user ID to delete the report for.
         date: str | null: The date to delete the report for. Format: YYYY-MM-DD. If not provided, the report will be named "latest".
tests/nat/builder/test_builder.py (1)

891-934: Fix shadowed variable; assertion is tautological.

workflow_config is redefined to workflow.config, making workflow_config.workflow == workflow_config.workflow meaningless. Compare built config to the original workflow config instead.

@@
-    function_config = FunctionReturningFunctionConfig()
-    workflow_config = FunctionReturningFunctionConfig()
+    function_config = FunctionReturningFunctionConfig()
+    workflow_cfg = FunctionReturningFunctionConfig()
@@
-        await builder.set_workflow(workflow_config)
+        await builder.set_workflow(workflow_cfg)
@@
-        workflow_config = workflow.config
+        built_config = workflow.config
@@
-        assert workflow_config.general == general_config
-        assert workflow_config.functions == {"function1": function_config}
-        assert workflow_config.workflow == workflow_config.workflow
-        assert workflow_config.llms == {"llm1": llm_config}
-        assert workflow_config.embedders == {"embedder1": embedder_config}
-        assert workflow_config.memory == {"memory1": memory_config}
-        assert workflow_config.retrievers == {"retriever1": retriever_config}
-        assert workflow_config.object_stores == {"object_store1": object_store_config}
-        assert workflow_config.ttc_strategies == {"ttc_strategy": ttc_config}
+        assert built_config.general == general_config
+        assert built_config.functions == {"function1": function_config}
+        assert built_config.workflow == workflow_cfg
+        assert built_config.llms == {"llm1": llm_config}
+        assert built_config.embedders == {"embedder1": embedder_config}
+        assert built_config.memory == {"memory1": memory_config}
+        assert built_config.retrievers == {"retriever1": retriever_config}
+        assert built_config.object_stores == {"object_store1": object_store_config}
+        assert built_config.ttc_strategies == {"ttc_strategy": ttc_config}
♻️ Duplicate comments (7)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (4)

147-150: Signature narrower than abstract; widen to accept FunctionGroupRef.
Prevents type-checking drift and override mismatch.

-    async def add_function_group(self, name: str, config: FunctionGroupBaseConfig) -> FunctionGroup:
+    async def add_function_group(self, name: str | FunctionGroupRef, config: FunctionGroupBaseConfig) -> FunctionGroup:
         """Mock implementation - not used in tool testing."""
         raise NotImplementedError("Mock implementation does not support add_function_group")

20-20: Import framework and ref types to match Builder API.
Prepares for widened method signatures below.

 from collections.abc import Sequence
+from nat.builder.framework_enum import LLMFrameworkEnum
+from nat.data_models.component_ref import FunctionRef, FunctionGroupRef

159-162: Widen get_function_group_config signature.
Matches abstract definition.

-    def get_function_group_config(self, name: str) -> FunctionGroupBaseConfig:
+    def get_function_group_config(self, name: str | FunctionGroupRef) -> FunctionGroupBaseConfig:
         """Mock implementation."""
         return FunctionGroupBaseConfig()

179-182: Match Builder.get_tools signature and avoid ARG002.
Accept refs; annotate return; mark args as intentionally unused.

-    def get_tools(self, tool_names: Sequence[str], wrapper_type):
-        """Mock implementation."""
-        return []
+    def get_tools(
+        self,
+        tool_names: Sequence[str | FunctionRef | FunctionGroupRef],
+        wrapper_type: LLMFrameworkEnum | str,
+    ) -> list[typing.Any]:
+        """Mock implementation."""
+        _ = (tool_names, wrapper_type)
+        return []
src/nat/data_models/config.py (2)

359-361: WorkflowAnnotation should accept FunctionGroup configs as well.
Else workflows defined as a function group will fail validation.

-        WorkflowAnnotation = typing.Annotated[(type_registry.compute_annotation(FunctionBaseConfig)),
-                                              Discriminator(TypedBaseModel.discriminator)]
+        WorkflowAnnotation = typing.Annotated[
+            (
+                type_registry.compute_annotation(FunctionBaseConfig)
+                | type_registry.compute_annotation(FunctionGroupBaseConfig)
+            ),
+            Discriminator(TypedBaseModel.discriminator),
+        ]

63-66: Workflow suggestions should include both functions and function groups.
Improves diagnostics for mixed workflows.

-            if (info.field_name in ('workflow', 'functions')):
-                registered_keys = GlobalTypeRegistry.get().get_registered_functions()
+            if info.field_name == 'workflow':
+                registered_keys = (
+                    list(GlobalTypeRegistry.get().get_registered_functions())
+                    + list(GlobalTypeRegistry.get().get_registered_function_groups())
+                )
+            elif info.field_name == 'functions':
+                registered_keys = GlobalTypeRegistry.get().get_registered_functions()
             elif (info.field_name == "function_groups"):
                 registered_keys = GlobalTypeRegistry.get().get_registered_function_groups()
src/nat/builder/workflow_builder.py (1)

1157-1167: ChildBuilder.get_tools(): normalize keys before membership checks.
FunctionGroupRef instances won’t match without normalization.

-        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
-        for tool_name in tool_names:
-            if tool_name in self._workflow_builder._function_groups:
-                self._dependencies.add_function_group(tool_name)
-            else:
-                self._dependencies.add_function(tool_name)
+        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
+        for tool_name in tool_names:
+            key = str(tool_name)
+            if key in self._workflow_builder._function_groups:
+                self._dependencies.add_function_group(key)
+            else:
+                self._dependencies.add_function(key)
         return tools
🧹 Nitpick comments (12)
examples/object_store/user_report/configs/config_redis.yml (3)

41-46: Preserve formatting in multi-line descriptions.

Use literal block scalars (|) to retain line breaks for Args lists; folded scalars (>) collapse newlines.

Apply:

-    get_description: >
+    get_description: |
       Fetches user diagnostic report from the bucket given a user ID and date.
       Args:
         user_id: str: The user ID to fetch the report for.
         date: str | null: The date to fetch the report for. Format: YYYY-MM-DD. If not provided, the latest report will be fetched.

46-53: Clarify conflict behavior for put to avoid unintended overwrites.

Be explicit that put must not overwrite existing content and should return a conflict; tighten wording.

-    put_description: >
-      Inserts a new user diagnostic report into the bucket given a user ID and date. If the report already exists, it will fail.
-      If it does fail, never delete the existing report to replace it. Just let the user know that the report already exists.
+    put_description: |
+      Inserts a new user diagnostic report into the bucket given a user ID and date.
+      If a report already exists for the (user_id, date) key, do not overwrite it; return a conflict error.
+      Never delete or replace an existing report as part of this operation.
       Args:
         report: str: The report to put into the bucket.
         user_id: str: The user ID to put the report for.
         date: str | null: The date to put the report for. Format: YYYY-MM-DD. If not provided, the report will be named "latest".

53-60: State “upsert” semantics for update; ensure idempotency.

Spell out that update should create when missing and be idempotent.

-    update_description: >
-      Updates a user diagnostic report in the bucket given a user ID and date. If the report does not exist, it will behave as
-      if the report was created. Do not delete the existing report to replace it.
+    update_description: |
+      Updates a user diagnostic report for the given user_id and date.
+      If the report does not exist, create it (upsert semantics). Do not delete/recreate existing reports.
+      The operation should be idempotent for the same (user_id, date, report) inputs.
       Args:
         report: str: The report to update in the bucket.
         user_id: str: The user ID to update the report for.
         date: str | null: The date to update the report for. Format: YYYY-MM-DD. If not provided, the report will be named "latest".
src/nat/data_models/function.py (1)

51-56: Add explicit return type on model validator (nit).
Helps pyright/ruff consistency.

Apply:

-    @model_validator(mode="after")
-    def validate_include_exclude(self):
+    @model_validator(mode="after")
+    def validate_include_exclude(self) -> "FunctionGroupBaseConfig":
         if self.include and self.exclude:
             raise ValueError("include and exclude cannot be used together")
         return self
tests/nat/agent/test_reasoning_agent.py (3)

141-146: Annotate mutable class attributes as ClassVar to silence RUF012.
These are class-level sets; mark as ClassVar.

Apply:

+from typing import ClassVar
@@
-    class FakeDeps:
-        functions = {"SomeTool"}
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"SomeTool"}
+        function_groups: ClassVar[set[str]] = set()

305-310: Same ClassVar fix here.
Replicate the annotation to avoid linter noise.

-    class FakeDeps:
-        functions = {"ToolA", "ToolB"}
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"ToolA", "ToolB"}
+        function_groups: ClassVar[set[str]] = set()

380-385: Same ClassVar fix for the empty-tools case.
Prevents repeated RUF012.

-    class FakeDeps:
-        functions = set()
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = set()
+        function_groups: ClassVar[set[str]] = set()
src/nat/builder/function.py (1)

401-406: Nit: compile the regex once.
Micro-optimization; avoids recompiling per call.

+NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
@@
-        if not re.match(r"^[a-zA-Z0-9_-]+$", name):
+        if NAME_RE.fullmatch(name) is None:
             raise ValueError(f"Function name can only contain letters, numbers, underscores, and hyphens: {name}")

(Add NAME_RE near the imports.)

src/nat/builder/workflow_builder.py (2)

396-404: Seed group dependencies under the instance key to avoid stray entries.
Currently initializes under config.type then stores under name.

-        self.current_function_group_building = config.type
-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        self.current_function_group_building = config.type
+        # Seed dependencies under the instance key
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies

561-577: Add return type for get_tool (nit) and preserve traceback.
Return annotation improves parity with abstract API.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
         ...
-        except Exception as e:
-            logger.error("Error fetching tool `%s`: %s", fn_name, e)
-            raise
+        except Exception as e:
+            logger.error("Error fetching tool `%s`: %s", fn_name, e)
+            raise
tests/nat/builder/test_builder.py (2)

286-419: Fix Ruff TRY003/ARG001 in function-group registrations.

  • TRY003: these raises are intentional for tests; silence with inline noqa.
  • ARG001: rename unused parameter in register_failing_function_group.

Apply:

@@
-        if config.raise_error:
-            raise ValueError("Function group initialization failed")
+        if config.raise_error:
+            raise ValueError("Function group initialization failed")  # noqa: TRY003
@@
-        if config.raise_error:
-            raise ValueError("Function group initialization failed")
+        if config.raise_error:
+            raise ValueError("Function group initialization failed")  # noqa: TRY003
@@
-        if config.raise_error:
-            raise ValueError("Function group initialization failed")
+        if config.raise_error:
+            raise ValueError("Function group initialization failed")  # noqa: TRY003
@@
-        if config.raise_error:
-            raise ValueError("Function group initialization failed")
+        if config.raise_error:
+            raise ValueError("Function group initialization failed")  # noqa: TRY003
@@
-    async def register_failing_function_group(config: FailingFunctionGroupConfig, _builder: Builder):
+    async def register_failing_function_group(_config: FailingFunctionGroupConfig, _builder: Builder):
@@
-        raise ValueError("Function group initialization failed")
+        raise ValueError("Function group initialization failed")  # noqa: TRY003

1054-1065: Prefix check should use config type, not instance name.

Function keys are namespaced by config.type (default_function_group), not the builder-assigned name (empty_group). While it still passes (zero functions), it’s clearer to assert on the actual prefix used by FunctionGroup.

-        included_functions = [k for k in builder._functions.keys() if k.startswith("empty_group.")]
+        included_functions = [k for k in builder._functions.keys() if k.startswith("default_function_group.")]
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e049ca4 and 164e4ec.

📒 Files selected for processing (14)
  • docs/source/extend/function-groups.md (1 hunks)
  • docs/source/workflows/function-groups.md (1 hunks)
  • examples/object_store/user_report/README.md (8 hunks)
  • examples/object_store/user_report/configs/config_redis.yml (2 hunks)
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5 hunks)
  • src/nat/agent/reasoning_agent/reasoning_agent.py (1 hunks)
  • src/nat/builder/function.py (3 hunks)
  • src/nat/builder/workflow_builder.py (17 hunks)
  • src/nat/cli/register_workflow.py (4 hunks)
  • src/nat/data_models/config.py (10 hunks)
  • src/nat/data_models/function.py (2 hunks)
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py (1 hunks)
  • tests/nat/agent/test_reasoning_agent.py (3 hunks)
  • tests/nat/builder/test_builder.py (16 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/source/extend/function-groups.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • examples/object_store/user_report/README.md
  • src/nat/cli/register_workflow.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • docs/source/workflows/function-groups.md
🧰 Additional context used
📓 Path-based instructions (12)
**/configs/**/*.y?(a)ml

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Configuration files consumed by code must live in a neighbouring configs/ directory

Files:

  • examples/object_store/user_report/configs/config_redis.yml
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • examples/object_store/user_report/configs/config_redis.yml
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • examples/object_store/user_report/configs/config_redis.yml
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/object_store/user_report/configs/config_redis.yml
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

Files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Unit tests must live under tests/ and use configured markers (e2e, integration, etc.)

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py

⚙️ CodeRabbit configuration file

tests/**/*.py: - Ensure that tests are comprehensive, cover edge cases, and validate the functionality of the code. - Test functions should be named using the test_ prefix, using snake_case. - Any frequently repeated code should be extracted into pytest fixtures. - Pytest fixtures should define the name argument when applying the pytest.fixture decorator. The fixture
function being decorated should be named using the fixture_ prefix, using snake_case. Example:
@pytest.fixture(name="my_fixture")
def fixture_my_fixture():
pass

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
**/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/tests/**/*.py: Test functions must use the test_ prefix and snake_case
Extract repeated test code into pytest fixtures; fixtures should set name=... in @pytest.fixture and functions named with fixture_ prefix
Mark expensive tests with @pytest.mark.slow or @pytest.mark.integration
Use pytest with pytest-asyncio for async code; mock external services with pytest_httpserver or unittest.mock

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
packages/*/src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

packages/*/src/**/*.py: All importable Python code in packages must live under packages//src/
All public APIs in packaged code require Python 3.11+ type hints; prefer typing/collections.abc; use typing.Annotated when useful
Provide Google-style docstrings for public APIs in packages; first line concise with a period; use backticks for code entities

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
packages/**/*

⚙️ CodeRabbit configuration file

packages/**/*: - This directory contains optional plugin packages for the toolkit, each should contain a pyproject.toml file. - The pyproject.toml file should declare a dependency on nvidia-nat or another package with a name starting
with nvidia-nat-. This dependency should be declared using ~=<version>, and the version should be a two
digit version (ex: ~=1.0).

  • Not all packages contain Python code, if they do they should also contain their own set of tests, in a
    tests/ directory at the same level as the pyproject.toml file.

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
🧠 Learnings (8)
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are sibling classes that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other and the order doesn't matter for classification purposes.

Applied to files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are separate class hierarchies that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other.

Applied to files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:30:12.178Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.

Applied to files:

  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.

Applied to files:

  • src/nat/builder/workflow_builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:29:34.897Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/workflow_builder.py:373-410
Timestamp: 2025-08-28T14:29:34.897Z
Learning: ComponentRef objects (like FunctionGroupRef) automatically convert to strings when used as dictionary keys in Python, so explicit str() conversions are not required when using them as dict keys.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T23:22:41.742Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-08-28T23:22:41.742Z
Learning: Applies to **/*.py : When re-raising exceptions, use bare `raise` and log with logger.error(), not logger.exception()

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.

Applied to files:

  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T20:03:37.650Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.

Applied to files:

  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
🧬 Code graph analysis (7)
src/nat/data_models/config.py (2)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/cli/type_registry.py (3)
  • get (1049-1050)
  • get_registered_function_groups (535-541)
  • compute_annotation (997-1041)
src/nat/builder/workflow_builder.py (8)
src/nat/builder/function.py (5)
  • FunctionGroup (350-526)
  • add_function (371-411)
  • Function (45-300)
  • get_included_functions (488-512)
  • get_accessible_functions (441-463)
src/nat/data_models/component_ref.py (12)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
  • component_group (69-76)
  • component_group (90-91)
  • component_group (101-102)
  • component_group (112-113)
  • component_group (123-124)
  • component_group (134-135)
  • component_group (145-146)
  • component_group (156-157)
  • component_group (167-168)
  • component_group (178-179)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/data_models/function_dependencies.py (5)
  • FunctionDependencies (21-80)
  • add_function (61-62)
  • add_function_group (64-65)
  • add_object_store (76-77)
  • add_retriever (79-80)
src/nat/data_models/config.py (1)
  • Config (238-423)
src/nat/builder/workflow.py (2)
  • Workflow (41-160)
  • from_entry_fn (128-160)
src/nat/builder/builder.py (11)
  • get_function_group (79-80)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function (75-76)
  • get_function_config (90-91)
  • get_function_group_config (94-95)
  • get_function_group_dependencies (280-281)
  • get_tools (110-113)
  • get_tool (116-117)
  • add_object_store (157-158)
  • add_retriever (217-218)
src/nat/profiler/utils.py (1)
  • detect_llm_frameworks_in_build_fn (36-144)
tests/nat/builder/test_builder.py (5)
src/nat/builder/function.py (8)
  • Function (45-300)
  • FunctionGroup (350-526)
  • add_function (371-411)
  • get_accessible_functions (441-463)
  • get_all_functions (514-526)
  • get_included_functions (488-512)
  • get_excluded_functions (465-486)
  • get_config (413-422)
src/nat/cli/register_workflow.py (2)
  • register_function_group (180-211)
  • register_function (148-177)
src/nat/cli/type_registry.py (4)
  • register_function_group (499-515)
  • register_function (477-485)
  • get_function_group (517-533)
  • get_function (487-493)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (11)
  • add_function (412-423)
  • add_function (1111-1112)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_function (451-457)
  • get_function (1119-1125)
  • dependencies (1107-1108)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5)
src/nat/builder/builder.py (6)
  • Builder (64-281)
  • add_function_group (71-72)
  • get_function_group (79-80)
  • get_function_group_config (94-95)
  • get_tools (110-113)
  • get_function_group_dependencies (280-281)
src/nat/builder/function.py (2)
  • Function (45-300)
  • FunctionGroup (350-526)
src/nat/data_models/function.py (2)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (10)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_tools (526-559)
  • get_tools (1157-1166)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
src/nat/data_models/function_dependencies.py (2)
  • add_function_group (64-65)
  • FunctionDependencies (21-80)
src/nat/builder/function.py (3)
src/nat/data_models/function.py (3)
  • EmptyFunctionConfig (58-59)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/context.py (1)
  • Context (93-277)
src/nat/builder/workflow_builder.py (2)
  • add_function (412-423)
  • add_function (1111-1112)
src/nat/data_models/function.py (1)
src/nat/data_models/common.py (2)
  • TypedBaseModel (93-168)
  • BaseModelRegistryTag (88-90)
tests/nat/agent/test_reasoning_agent.py (2)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)
  • get_function_dependencies (284-286)
  • get_function_group_dependencies (288-290)
src/nat/builder/workflow_builder.py (4)
  • get_function_dependencies (514-517)
  • get_function_dependencies (1296-1297)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
🪛 Ruff (0.12.2)
src/nat/builder/workflow_builder.py

406-407: Prefer TypeError exception for invalid type

(TRY004)


406-407: Avoid specifying long messages outside the exception class

(TRY003)


417-417: Avoid specifying long messages outside the exception class

(TRY003)


431-431: Avoid specifying long messages outside the exception class

(TRY003)


442-442: Avoid specifying long messages outside the exception class

(TRY003)


455-455: Avoid specifying long messages outside the exception class

(TRY003)


464-464: Avoid specifying long messages outside the exception class

(TRY003)


473-473: Avoid specifying long messages outside the exception class

(TRY003)


482-482: Avoid specifying long messages outside the exception class

(TRY003)


536-536: Avoid specifying long messages outside the exception class

(TRY003)


541-541: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/builder/test_builder.py

292-292: Avoid specifying long messages outside the exception class

(TRY003)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Avoid specifying long messages outside the exception class

(TRY003)


363-363: Avoid specifying long messages outside the exception class

(TRY003)


390-390: Avoid specifying long messages outside the exception class

(TRY003)


413-413: Unused function argument: config

(ARG001)


417-417: Avoid specifying long messages outside the exception class

(TRY003)

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py

157-157: Avoid specifying long messages outside the exception class

(TRY003)


159-159: Unused method argument: name

(ARG002)


179-179: Unused method argument: tool_names

(ARG002)


179-179: Unused method argument: wrapper_type

(ARG002)


288-288: Unused method argument: fn_name

(ARG002)

src/nat/builder/function.py

402-402: Avoid specifying long messages outside the exception class

(TRY003)


404-404: Avoid specifying long messages outside the exception class

(TRY003)


406-406: Avoid specifying long messages outside the exception class

(TRY003)


432-433: Avoid specifying long messages outside the exception class

(TRY003)


481-482: Avoid specifying long messages outside the exception class

(TRY003)


506-508: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/data_models/function.py

48-48: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/agent/test_reasoning_agent.py

141-141: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


142-142: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


305-305: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


306-306: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


380-380: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


381-381: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (31)
examples/object_store/user_report/configs/config_redis.yml (2)

70-70: Verify support for function groups in tool_names
I didn’t find any code or docs referencing tool_names in group resolution—please manually confirm that WorkflowBuilder and tool resolution correctly handle group names (or add explicit include/exclude settings if you only need a subset).


37-40: No changes needed – include/exclude fields are already provided for scoping, and the config type “user_report” with its object_store field is correctly defined.

src/nat/data_models/function.py (5)

18-21: Pydantic v2 imports look correct.
Using Field/field_validator/model_validator is appropriate here.


30-34: Clear public docstring for the new base config.
Good, concise description.


35-42: Good defaults for include/exclude lists.
default_factory=list avoids shared state; field descriptions are clear.


44-50: Uniqueness validator LGTM.
Covers both fields via multi-field validator.


64-65: TypeVar export looks good.
Useful for downstream typing.

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)

63-66: Mock registration for function groups: LGTM.
Simple and consistent with other mocks.


288-291: Stub for group dependencies is fine.
Return empty FunctionDependencies.

src/nat/data_models/config.py (4)

141-143: Good: dict fields use default_factory.
Prevents shared mutable defaults; compliant with ruff.


246-265: Function and Function Group fields added with safe defaults.
Looks correct and backward compatible.


287-288: Nice: summary includes Function Groups.
Small UX improvement.


339-342: FunctionGroupsAnnotation build is correct.
Keeps annotations in sync with registry.

src/nat/builder/function.py (6)

17-17: Import of re is appropriate for name validation.


349-413: FunctionGroup core structure LGTM.
Clear API, typed, and documented; instance-name namespacing is good.


427-439: Exclude handling preserves order and qualifies names.
Good validation and mapping.


480-486: Excluded functions API LGTM.
Validates unknown names; returns namespaced keys.


505-513: Included functions API LGTM.
Deterministic order from include list; good error text.


523-526: get_all_functions returns fully-qualified keys.
Consistent addressing.

src/nat/builder/workflow_builder.py (7)

93-97: ConfiguredFunctionGroup dataclass: LGTM.
Keeps config/instance paired like other components.


158-160: Tracking _function_groups in builder is correct.
Symmetric with _functions.


176-179: Dependency tracking fields added: LGTM.
Enables per-group dependency surfacing.


284-288: Mirror fix for instances set (relies on exposed_functions).
After the above change, this block is correct.

Ensure no duplication of group-exposed functions in function_instances.


526-559: get_tools(): normalization and de-dup logic LGTM.
Handles FunctionRef/FunctionGroupRef correctly before lookup.


1054-1056: populate_builder: function group addition wiring LGTM.
Matches new ComponentGroup.FUNCTION_GROUPS.


243-249: Replace non-existent config.expose
FunctionGroupBaseConfig only defines include/exclude, so v.config.expose will raise an AttributeError. Compute exposed functions via each group’s get_included_functions() instead:

-        # Get all functions which are exposed by function groups
-        exposed_functions = {f"{k}.{n}" for k, v in self._function_groups.items() for n in v.config.expose}
+        # Get all functions exposed (included) by function groups
+        exposed_functions: set[str] = set()
+        for group in self._function_groups.values():
+            exposed_functions.update(group.instance.get_included_functions().keys())

Ensure get_included_functions() exists on the function-group instance and correctly applies include/exclude.

⛔ Skipped due to learnings
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.
tests/nat/builder/test_builder.py (5)

19-23: Correct Pydantic imports added.

Good fix replacing/adding BaseModel and Field from pydantic; aligns with prior issues.


50-51: FunctionGroupBaseConfig import is appropriate.

Brings tests in sync with the new Function Group surface.


114-148: Test configs use Field(default_factory=...) correctly.

Nice job avoiding mutable defaults and removing unsupported frozen args.


1329-1344: Custom instance name behavior validated well.

Good coverage for namespacing override via instance_name.


783-839: Acknowledge async signature aligns with implementation
Tests correctly await get_retriever_config, which is declared async, while get_object_store_config and get_llm_config remain synchronous by design—no test changes required.

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

Caution

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

⚠️ Outside diff range comments (2)
src/nat/builder/workflow_builder.py (2)

561-577: Add return type; include exc_info to aid debugging while preserving traceback.

Public API must have return annotations per repo guidelines.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
@@
-        except Exception as e:
-            logger.error("Error fetching tool `%s`: %s", fn_name, e)
-            raise
+        except Exception as e:
+            logger.error("Error fetching tool `%s`: %s", fn_name, e, exc_info=True)
+            raise

1168-1175: Annotate return type and normalize dependency key.

Ensures API typing compliance and consistent dependency tracking.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
         # If a function tries to get another function as a tool, we assume it uses it
         fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
 
-        self._dependencies.add_function(fn_name)
+        self._dependencies.add_function(str(fn_name))
 
         return fn
♻️ Duplicate comments (9)
examples/object_store/user_report/configs/config_mem.yml (1)

70-70: Add test to verify tool_names group expansion and ordering.

Please ensure a test asserts that tool_names: [user_report] expands to the expected ordered, namespaced tools.

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5)

159-162: Match config getter signature and silence ARG002.

Accept FunctionGroupRef and underscore the unused param.

-    def get_function_group_config(self, name: str) -> FunctionGroupBaseConfig:
+    def get_function_group_config(self, _name: str | FunctionGroupRef) -> FunctionGroupBaseConfig:
         """Mock implementation."""
         return FunctionGroupBaseConfig()

20-20: Import missing types to align with Builder API.

Bring in the type aliases used by abstract signatures to avoid pyright/ruff drift.

-from collections.abc import Sequence
+from collections.abc import Sequence
+from nat.builder.framework_enum import LLMFrameworkEnum
+from nat.data_models.component_ref import FunctionRef, FunctionGroupRef

147-150: Align add_function_group signature with Builder.

Accept FunctionGroupRef in addition to str.

-    async def add_function_group(self, name: str, config: FunctionGroupBaseConfig) -> FunctionGroup:
+    async def add_function_group(self, name: str | FunctionGroupRef, config: FunctionGroupBaseConfig) -> FunctionGroup:

151-158: Widen get_function_group signature and return a spec’ed mock.

Prevents interface drift; safer attribute access.

-    def get_function_group(self, name: str) -> FunctionGroup:
+    def get_function_group(self, name: str | FunctionGroupRef) -> FunctionGroup:
         """Return a mock function group if one is configured."""
-        if name in self._mocks:
-            mock_fn_group = MagicMock(spec=FunctionGroup)
-            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[name])
+        key = str(name)
+        if key in self._mocks:
+            mock_fn_group = MagicMock(spec=FunctionGroup)
+            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[key])
             return mock_fn_group
         raise ValueError(f"Function group '{name}' not mocked. Use mock_function_group() to add it.")

179-182: Widen get_tools signature; type wrapper; silence unused arg warnings.

Matches Builder and fixes ARG002.

-    def get_tools(self, tool_names: Sequence[str], wrapper_type):
+    def get_tools(
+        self,
+        tool_names: Sequence[str | FunctionRef | FunctionGroupRef],
+        wrapper_type: LLMFrameworkEnum | str,
+    ) -> list[typing.Any]:
         """Mock implementation."""
-        return []
+        _ = (tool_names, wrapper_type)
+        return []
examples/object_store/user_report/configs/config_mysql.yml (1)

74-74: Test group expansion and order.

Add/ensure a test validates expansion of [user_report] to the expected ordered tool list.

examples/object_store/user_report/configs/config_redis.yml (1)

73-73: Test group expansion and order.

Same as other envs—please cover [user_report] expansion in tests.

src/nat/builder/workflow_builder.py (1)

1157-1167: Normalize keys when recording dependencies for get_tools in ChildBuilder.

tool_name may be a FunctionRef/FunctionGroupRef; membership and recording should use normalized keys. (Reiterating prior feedback.)

-        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
-        for tool_name in tool_names:
-            if tool_name in self._workflow_builder._function_groups:
-                self._dependencies.add_function_group(tool_name)
-            else:
-                self._dependencies.add_function(tool_name)
+        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
+        for tool_name in tool_names:
+            key = str(tool_name)
+            if key in self._workflow_builder._function_groups:
+                self._dependencies.add_function_group(key)
+            else:
+                self._dependencies.add_function(key)
🧹 Nitpick comments (13)
src/nat/data_models/function.py (1)

44-55: Minor: tighten lint noise (TRY003) message wording.

Ruff flags long exception messages; your messages are already short, but to silence TRY003 consistently, prefer concise phrasing.

Apply:

-            raise ValueError("Function names must be unique")
+            raise ValueError("Function names must be unique.")
-            raise ValueError("include and exclude cannot be used together")
+            raise ValueError("`include` and `exclude` are mutually exclusive.")
tests/nat/agent/test_reasoning_agent.py (3)

141-143: Annotate mutable class attributes as ClassVar to satisfy Ruff (RUF012).

Declare these sets as ClassVar or move them to init.

Apply:

-    class FakeDeps:
-        functions = {"SomeTool"}
-        function_groups = set()
+    class FakeDeps:
+        functions: typing.ClassVar[set[str]] = {"SomeTool"}
+        function_groups: typing.ClassVar[set[str]] = set()

Also add at file top:

from typing import ClassVar  # or use typing.ClassVar in annotations

305-307: Repeat: mark sets as ClassVar.

Same fix as above for this FakeDeps definition.

-    class FakeDeps:
-        functions = {"ToolA", "ToolB"}
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"ToolA", "ToolB"}
+        function_groups: ClassVar[set[str]] = set()

380-385: Repeat: mark sets as ClassVar.

Same change here to avoid RUF012.

-    class FakeDeps:
-        functions = set()
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = set()
+        function_groups: ClassVar[set[str]] = set()

Outside this hunk, add:

from typing import ClassVar
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)

288-291: Silence ARG002 for unused parameter.

Prefix with underscore in this mock.

-    def get_function_group_dependencies(self, fn_name: str) -> FunctionDependencies:
+    def get_function_group_dependencies(self, _fn_name: str) -> FunctionDependencies:
         """Mock implementation."""
         return FunctionDependencies()
src/nat/builder/function.py (3)

431-439: Shorten ValueError to satisfy Ruff TRY003.

Current message spans two sentences; condense to list only the unknown names.

-        if set(self._config.exclude) - set(self._functions.keys()):
-            raise ValueError(f"Function group {self._instance_name} excludes functions that are not found in the group."
-                             f" Available functions: {list(self._functions.keys())}")
+        missing = set(self._config.exclude) - set(self._functions.keys())
+        if missing:
+            raise ValueError(f"Unknown excludes: {sorted(missing)}")

480-486: Same: condense error text for excludes.

Mirror the shorter style here.

-        if set(self._config.exclude) - set(self._functions.keys()):
-            raise ValueError(f"Function group {self._instance_name} excludes functions that are not found in the group."
-                             f" Available functions: {list(self._functions.keys())}")
+        missing = set(self._config.exclude) - set(self._functions.keys())
+        if missing:
+            raise ValueError(f"Unknown excludes: {sorted(missing)}")

505-509: Same: condense error text for includes.

Short message reduces TRY003 noise.

-        if set(self._config.include) - set(self._functions.keys()):
-            raise ValueError(
-                f"Function group {self._instance_name} includes functions that are not found in the group. "
-                f"Available functions: {list(self._functions.keys())}")
+        missing = set(self._config.include) - set(self._functions.keys())
+        if missing:
+            raise ValueError(f"Unknown includes: {sorted(missing)}")
src/nat/builder/workflow_builder.py (5)

373-410: Seed group dependencies under the instance key to avoid stray entries.

You initialize at config.type then store at name, leaving an extra key.

-        self.current_function_group_building = config.type
-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        self.current_function_group_building = config.type
+        # Empty set of dependencies for this function group (by instance key)
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies

405-407: Exception type: prefer TypeError for wrong return type.

Matches Ruff TRY004 and intent.

-            raise ValueError("Expected a FunctionGroup object to be returned from the function group builder. "
+            raise TypeError("Expected a FunctionGroup object to be returned from the function group builder. "
                              f"Got {type(build_result)}")

438-446: Avoid double calls to get_included_functions() and guard once.

Saves a dict build and keeps logic in one place.

-        for k in build_result.instance.get_included_functions():
+        included = build_result.instance.get_included_functions()
+        for k in included:
             if k in self._functions:
                 raise ValueError(f"Exposed function `{k}` from group `{name}` conflicts with an existing function")
-        self._functions.update({
-            k: ConfiguredFunction(config=v.config, instance=v)
-            for k, v in build_result.instance.get_included_functions().items()
-        })
+        self._functions.update({k: ConfiguredFunction(config=v.config, instance=v) for k, v in included.items()})

525-559: Deduplicate emitted tools when a group and its member(s) are both requested.

Currently the same fn can be returned twice (once via group, once via direct name).

-        tools = []
+        tools = []
         seen = set()
+        emitted: set[str] = set()
@@
-            if n not in self._function_groups:
+            if n not in self._function_groups:
                 # the passed tool name is probably a function
                 if is_function_group_ref:
                     raise ValueError(f"Function group `{n}` not found in the list of function groups")
-                tools.append(self.get_tool(n, wrapper_type))
+                if n not in emitted:
+                    tools.append(self.get_tool(n, wrapper_type))
+                    emitted.add(n)
                 continue
@@
-            for fn_name, fn_instance in current_function_group.instance.get_accessible_functions().items():
+            for fn_name, fn_instance in current_function_group.instance.get_accessible_functions().items():
                 try:
                     # Wrap in the correct wrapper and add to tools list
-                    tools.append(tool_wrapper_reg.build_fn(fn_name, fn_instance, self))
+                    if fn_name in emitted:
+                        continue
+                    tools.append(tool_wrapper_reg.build_fn(fn_name, fn_instance, self))
+                    emitted.add(fn_name)
                 except Exception:
                     logger.error("Error fetching tool `%s`", fn_name, exc_info=True)
                     raise

1048-1048: Grammar nit: “an object store.”

Tiny readability fix.

-                # Instantiate a object store client
+                # Instantiate an object store client
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 164e4ec and 06fa121.

📒 Files selected for processing (17)
  • docs/source/extend/function-groups.md (1 hunks)
  • docs/source/workflows/function-groups.md (1 hunks)
  • examples/object_store/user_report/README.md (8 hunks)
  • examples/object_store/user_report/configs/config_mem.yml (1 hunks)
  • examples/object_store/user_report/configs/config_mysql.yml (1 hunks)
  • examples/object_store/user_report/configs/config_redis.yml (1 hunks)
  • examples/object_store/user_report/configs/config_s3.yml (1 hunks)
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5 hunks)
  • src/nat/agent/reasoning_agent/reasoning_agent.py (1 hunks)
  • src/nat/builder/function.py (3 hunks)
  • src/nat/builder/workflow_builder.py (17 hunks)
  • src/nat/cli/register_workflow.py (4 hunks)
  • src/nat/data_models/config.py (10 hunks)
  • src/nat/data_models/function.py (2 hunks)
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py (1 hunks)
  • tests/nat/agent/test_reasoning_agent.py (3 hunks)
  • tests/nat/builder/test_builder.py (16 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/source/workflows/function-groups.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • examples/object_store/user_report/configs/config_s3.yml
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/register_workflow.py
  • examples/object_store/user_report/README.md
  • docs/source/extend/function-groups.md
🧰 Additional context used
📓 Path-based instructions (12)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • examples/object_store/user_report/configs/config_mem.yml
  • src/nat/builder/function.py
  • examples/object_store/user_report/configs/config_redis.yml
  • examples/object_store/user_report/configs/config_mysql.yml
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • examples/object_store/user_report/configs/config_mem.yml
  • src/nat/builder/function.py
  • examples/object_store/user_report/configs/config_redis.yml
  • examples/object_store/user_report/configs/config_mysql.yml
  • tests/nat/builder/test_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/configs/**/*.y?(a)ml

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Configuration files consumed by code must live in a neighbouring configs/ directory

Files:

  • examples/object_store/user_report/configs/config_mem.yml
  • examples/object_store/user_report/configs/config_redis.yml
  • examples/object_store/user_report/configs/config_mysql.yml
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/object_store/user_report/configs/config_mem.yml
  • examples/object_store/user_report/configs/config_redis.yml
  • examples/object_store/user_report/configs/config_mysql.yml
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Unit tests must live under tests/ and use configured markers (e2e, integration, etc.)

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py

⚙️ CodeRabbit configuration file

tests/**/*.py: - Ensure that tests are comprehensive, cover edge cases, and validate the functionality of the code. - Test functions should be named using the test_ prefix, using snake_case. - Any frequently repeated code should be extracted into pytest fixtures. - Pytest fixtures should define the name argument when applying the pytest.fixture decorator. The fixture
function being decorated should be named using the fixture_ prefix, using snake_case. Example:
@pytest.fixture(name="my_fixture")
def fixture_my_fixture():
pass

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
**/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/tests/**/*.py: Test functions must use the test_ prefix and snake_case
Extract repeated test code into pytest fixtures; fixtures should set name=... in @pytest.fixture and functions named with fixture_ prefix
Mark expensive tests with @pytest.mark.slow or @pytest.mark.integration
Use pytest with pytest-asyncio for async code; mock external services with pytest_httpserver or unittest.mock

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
packages/*/src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

packages/*/src/**/*.py: All importable Python code in packages must live under packages//src/
All public APIs in packaged code require Python 3.11+ type hints; prefer typing/collections.abc; use typing.Annotated when useful
Provide Google-style docstrings for public APIs in packages; first line concise with a period; use backticks for code entities

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
packages/**/*

⚙️ CodeRabbit configuration file

packages/**/*: - This directory contains optional plugin packages for the toolkit, each should contain a pyproject.toml file. - The pyproject.toml file should declare a dependency on nvidia-nat or another package with a name starting
with nvidia-nat-. This dependency should be declared using ~=<version>, and the version should be a two
digit version (ex: ~=1.0).

  • Not all packages contain Python code, if they do they should also contain their own set of tests, in a
    tests/ directory at the same level as the pyproject.toml file.

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
🧠 Learnings (8)
📚 Learning: 2025-08-28T14:30:12.178Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.

Applied to files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.

Applied to files:

  • src/nat/builder/workflow_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:29:34.897Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/workflow_builder.py:373-410
Timestamp: 2025-08-28T14:29:34.897Z
Learning: ComponentRef objects (like FunctionGroupRef) automatically convert to strings when used as dictionary keys in Python, so explicit str() conversions are not required when using them as dict keys.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T23:22:41.742Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-08-28T23:22:41.742Z
Learning: Applies to **/*.py : When re-raising exceptions, use bare `raise` and log with logger.error(), not logger.exception()

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are sibling classes that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other and the order doesn't matter for classification purposes.

Applied to files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are separate class hierarchies that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other.

Applied to files:

  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/config.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T20:03:37.650Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.

Applied to files:

  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.

Applied to files:

  • src/nat/builder/function.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/function.py
🧬 Code graph analysis (7)
src/nat/builder/workflow_builder.py (8)
src/nat/builder/function.py (5)
  • FunctionGroup (350-526)
  • add_function (371-411)
  • Function (45-300)
  • get_included_functions (488-512)
  • get_accessible_functions (441-463)
src/nat/data_models/component_ref.py (12)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
  • component_group (69-76)
  • component_group (90-91)
  • component_group (101-102)
  • component_group (112-113)
  • component_group (123-124)
  • component_group (134-135)
  • component_group (145-146)
  • component_group (156-157)
  • component_group (167-168)
  • component_group (178-179)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/data_models/function_dependencies.py (5)
  • FunctionDependencies (21-80)
  • add_function (61-62)
  • add_function_group (64-65)
  • add_object_store (76-77)
  • add_retriever (79-80)
src/nat/data_models/config.py (1)
  • Config (238-423)
src/nat/builder/workflow.py (2)
  • Workflow (41-160)
  • from_entry_fn (128-160)
src/nat/builder/builder.py (11)
  • get_function_group (79-80)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function (75-76)
  • get_function_config (90-91)
  • get_function_group_config (94-95)
  • get_function_group_dependencies (280-281)
  • get_tools (110-113)
  • get_tool (116-117)
  • add_object_store (157-158)
  • add_retriever (217-218)
src/nat/profiler/utils.py (1)
  • detect_llm_frameworks_in_build_fn (36-144)
src/nat/data_models/config.py (2)
src/nat/data_models/function.py (3)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
  • EmptyFunctionConfig (58-59)
src/nat/cli/type_registry.py (4)
  • GlobalTypeRegistry (1044-1065)
  • get (1049-1050)
  • get_registered_function_groups (535-541)
  • compute_annotation (997-1041)
src/nat/builder/function.py (2)
src/nat/data_models/function.py (3)
  • EmptyFunctionConfig (58-59)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/context.py (1)
  • Context (93-277)
tests/nat/builder/test_builder.py (5)
src/nat/builder/function.py (8)
  • Function (45-300)
  • FunctionGroup (350-526)
  • add_function (371-411)
  • get_accessible_functions (441-463)
  • get_all_functions (514-526)
  • get_included_functions (488-512)
  • get_excluded_functions (465-486)
  • get_config (413-422)
src/nat/cli/register_workflow.py (2)
  • register_function_group (180-211)
  • register_function (148-177)
src/nat/cli/type_registry.py (4)
  • register_function_group (499-515)
  • register_function (477-485)
  • get_function_group (517-533)
  • get_function (487-493)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (11)
  • add_function (412-423)
  • add_function (1111-1112)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_function (451-457)
  • get_function (1119-1125)
  • dependencies (1107-1108)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (6)
src/nat/builder/builder.py (6)
  • Builder (64-281)
  • add_function_group (71-72)
  • get_function_group (79-80)
  • get_function_group_config (94-95)
  • get_tools (110-113)
  • get_function_group_dependencies (280-281)
src/nat/builder/function.py (2)
  • Function (45-300)
  • FunctionGroup (350-526)
src/nat/cli/type_registry.py (2)
  • GlobalTypeRegistry (1044-1065)
  • get_function_group (517-533)
src/nat/data_models/function.py (2)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (10)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_tools (526-559)
  • get_tools (1157-1166)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
src/nat/data_models/function_dependencies.py (2)
  • add_function_group (64-65)
  • FunctionDependencies (21-80)
src/nat/data_models/function.py (1)
src/nat/data_models/common.py (2)
  • TypedBaseModel (93-168)
  • BaseModelRegistryTag (88-90)
tests/nat/agent/test_reasoning_agent.py (2)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)
  • get_function_dependencies (284-286)
  • get_function_group_dependencies (288-290)
src/nat/builder/workflow_builder.py (4)
  • get_function_dependencies (514-517)
  • get_function_dependencies (1296-1297)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
🪛 Ruff (0.12.2)
src/nat/builder/workflow_builder.py

406-407: Prefer TypeError exception for invalid type

(TRY004)


406-407: Avoid specifying long messages outside the exception class

(TRY003)


417-417: Avoid specifying long messages outside the exception class

(TRY003)


431-431: Avoid specifying long messages outside the exception class

(TRY003)


442-442: Avoid specifying long messages outside the exception class

(TRY003)


455-455: Avoid specifying long messages outside the exception class

(TRY003)


464-464: Avoid specifying long messages outside the exception class

(TRY003)


473-473: Avoid specifying long messages outside the exception class

(TRY003)


482-482: Avoid specifying long messages outside the exception class

(TRY003)


536-536: Avoid specifying long messages outside the exception class

(TRY003)


541-541: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/builder/function.py

402-402: Avoid specifying long messages outside the exception class

(TRY003)


404-404: Avoid specifying long messages outside the exception class

(TRY003)


406-406: Avoid specifying long messages outside the exception class

(TRY003)


432-433: Avoid specifying long messages outside the exception class

(TRY003)


481-482: Avoid specifying long messages outside the exception class

(TRY003)


506-508: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/builder/test_builder.py

292-292: Avoid specifying long messages outside the exception class

(TRY003)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Avoid specifying long messages outside the exception class

(TRY003)


363-363: Avoid specifying long messages outside the exception class

(TRY003)


390-390: Avoid specifying long messages outside the exception class

(TRY003)


413-413: Unused function argument: config

(ARG001)


417-417: Avoid specifying long messages outside the exception class

(TRY003)

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py

157-157: Avoid specifying long messages outside the exception class

(TRY003)


159-159: Unused method argument: name

(ARG002)


179-179: Unused method argument: tool_names

(ARG002)


179-179: Unused method argument: wrapper_type

(ARG002)


288-288: Unused method argument: fn_name

(ARG002)

src/nat/data_models/function.py

48-48: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/agent/test_reasoning_agent.py

141-141: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


142-142: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


305-305: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


306-306: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


380-380: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


381-381: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (22)
src/nat/data_models/function.py (1)

30-42: Solid base config and typing.

Includes/excludes with validators and Google-style docstring look good. Default factories avoid shared state.

examples/object_store/user_report/configs/config_mem.yml (1)

34-47: Config consolidation to a Function Group looks good.

Group wiring and workflow reference are consistent with the new model.

Also applies to: 51-63, 68-75

examples/object_store/user_report/configs/config_mysql.yml (1)

38-71: MySQL config matches the Function Group pattern.

Looks consistent with mem/redis variants and workflow wiring.

Also applies to: 72-79

examples/object_store/user_report/configs/config_redis.yml (1)

37-70: Redis config consolidation LGTM.

Group definitions and workflow reference are consistent.

Also applies to: 71-78

src/nat/data_models/config.py (5)

23-24: Good: using Field(default_factory=...).

Avoids mutable defaults across dict-like sections.


62-66: Helpful error routing for function_groups.

Adding the function_groups branch in _process_validation_error improves messages.


339-342: Annotation rebuilding covers function_groups.

FunctionGroupsAnnotation and field hook look correct.

Also applies to: 384-388


359-361: Confirm workflow cannot be a FunctionGroup; update suggestions if needed.

If workflow is ever allowed to be a FunctionGroup, WorkflowAnnotation should union-in FunctionGroupBaseConfig, and suggestions for 'workflow' should include both functions and groups. If not, current typing is fine.


286-288: Nice touch: summary includes Function Groups.

Printout addition is useful.

src/nat/builder/function.py (3)

350-370: Well-scoped FunctionGroup with namespacing.

Constructor and internal mapping are clear; namespacing via instance_name is consistent.


371-412: Name validation and duplicate protection: good baseline.

Rejecting blanks, dots, and duplicates is appropriate; wrapping via LambdaFunction is clean.


514-526: Deterministic ordering preserved.

Iterating the ordered mapping to build fully-qualified keys is correct.

tests/nat/builder/test_builder.py (7)

20-22: LGTM!

The import updates correctly add the necessary dependencies for function group testing - BaseModel, Field, FunctionGroup, register_function_group, and FunctionGroupBaseConfig.

Also applies to: 27-27, 36-36, 50-50


114-148: LGTM!

The function group test configurations are well-designed and comprehensive:

  • Uses Field(default_factory=...) to avoid mutable default issues
  • Covers different scenarios: includes, excludes, empty, all functions
  • Includes a failing configuration for error testing
  • Good docstrings explaining each config's purpose

175-175: LGTM!

The parameter names have been correctly updated to _builder to follow the established naming convention in the codebase and avoid shadowing concerns.

Also applies to: 234-234, 242-242, 250-250, 263-263, 275-275, 278-278


286-418: LGTM!

The function group registration implementations are excellent:

  • Comprehensive coverage of different config types (includes/excludes/all/failing)
  • Proper error handling with configurable failure modes
  • Consistent function implementations across registrations
  • Good use of descriptive function documentation
  • Proper yield pattern for async generators

The test functions (add, multiply, subtract) provide good coverage for testing inclusion/exclusion scenarios.


443-443: LGTM!

The type: ignore annotations are appropriate for handling decorator type checking issues that don't affect runtime behavior.

Also applies to: 497-497


783-783: LGTM!

The function name updates correctly reflect the new async nature of retriever methods and the use of default GeneralConfig.

Also applies to: 818-818, 828-828, 836-836, 839-839, 893-893


936-1344: LGTM!

The function group test suite is comprehensive and well-structured:

Excellent test coverage:

  • Basic CRUD operations (add, get, get_config)
  • Inclusion/exclusion behavior validation
  • Edge cases (empty groups, all functions)
  • Error scenarios (name conflicts, invalid configs)
  • Integration with workflow builder
  • Function execution testing
  • Custom instance names

Good test practices:

  • Clear, descriptive test names and docstrings
  • Proper error assertion with specific error messages
  • Verification of both positive and negative cases
  • Integration testing that validates end-to-end functionality

Thorough validation:

  • Tests verify that included functions are accessible globally
  • Tests verify that excluded functions are not accessible globally but accessible through the group
  • Tests validate dependency tracking
  • Tests check configuration validation and error handling

The tests provide excellent coverage of the function group functionality and will catch regressions effectively.

src/nat/builder/workflow_builder.py (3)

93-97: New ConfiguredFunctionGroup dataclass looks correct.

Accurate types and placement; aligns with the FunctionGroup design.


158-160: Registries for function groups and dependencies initialized properly.

State separation between functions and function_groups is clear.

Also applies to: 176-179


243-249: Ignore removed config.expose comment: expose remains a valid field on FunctionGroupBaseConfig; no build break

Likely an incorrect or invalid review comment.

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

Caution

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

⚠️ Outside diff range comments (1)
src/nat/builder/workflow_builder.py (1)

1169-1175: ChildBuilder.get_tool(): record dependency with normalized name.

Ensures consistent str keys in FunctionDependencies.

-        fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
-
-        self._dependencies.add_function(fn_name)
+        key = str(fn_name)
+        fn = self._workflow_builder.get_tool(key, wrapper_type)
+        self._dependencies.add_function(key)
♻️ Duplicate comments (13)
src/nat/builder/component_utils.py (1)

261-263: Guard total_node_count for backward compatibility.

Older configs may lack function_groups; direct attribute access breaks the builder. Use getattr(..., {}) for all component dicts.

Apply:

-    total_node_count = (len(config.embedders) + len(config.functions) + len(config.function_groups) + len(config.llms) +
-                        len(config.memory) + len(config.object_stores) + len(config.retrievers) +
-                        len(config.ttc_strategies) + len(config.authentication) + 1)  # +1 for the workflow
+    total_node_count = (
+        len(getattr(config, "embedders", {}))
+        + len(getattr(config, "functions", {}))
+        + len(getattr(config, "function_groups", {}))
+        + len(getattr(config, "llms", {}))
+        + len(getattr(config, "memory", {}))
+        + len(getattr(config, "object_stores", {}))
+        + len(getattr(config, "retrievers", {}))
+        + len(getattr(config, "ttc_strategies", {}))
+        + len(getattr(config, "authentication", {}))
+        + 1  # +1 for the workflow
+    )

Optional hardening (outside this hunk): in config_to_dependency_objects, prefer
component_dict = getattr(config, group.value, {}) for consistency.

examples/object_store/user_report/configs/config_s3.yml (1)

16-22: Remove duplicate object_store under front_end.

Group-level object_store is authoritative; keeping both risks schema validation failure.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5)

20-24: Import missing types to match Builder API.

Required for widened signatures below.

 from collections.abc import Sequence
+from nat.builder.framework_enum import LLMFrameworkEnum
+from nat.data_models.component_ref import FunctionRef, FunctionGroupRef

Also applies to: 28-35


147-149: Align add_function_group signature with abstract base.

Accept FunctionGroupRef.

-    async def add_function_group(self, name: str, config: FunctionGroupBaseConfig) -> FunctionGroup:
+    async def add_function_group(self, name: str | FunctionGroupRef, config: FunctionGroupBaseConfig) -> FunctionGroup:
         """Mock implementation - not used in tool testing."""
         raise NotImplementedError("Mock implementation does not support add_function_group")

151-158: Align get_function_group signature and use spec’ed mock.

Prevents interface drift; safer mocking.

-    def get_function_group(self, name: str) -> FunctionGroup:
+    def get_function_group(self, name: str | FunctionGroupRef) -> FunctionGroup:
         """Return a mock function group if one is configured."""
-        if name in self._mocks:
-            mock_fn_group = MagicMock(spec=FunctionGroup)
-            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[name])
+        key = str(name)
+        if key in self._mocks:
+            mock_fn_group = MagicMock(spec=FunctionGroup)
+            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[key])
             return mock_fn_group
         raise ValueError(f"Function group '{name}' not mocked. Use mock_function_group() to add it.")

159-162: Align get_function_group_config signature with base.

-    def get_function_group_config(self, name: str) -> FunctionGroupBaseConfig:
+    def get_function_group_config(self, name: str | FunctionGroupRef) -> FunctionGroupBaseConfig:
         """Mock implementation."""
         return FunctionGroupBaseConfig()

179-182: Widen get_tools signature and type wrapper.

Matches Builder.get_tools.

-    def get_tools(self, tool_names: Sequence[str], wrapper_type):
+    def get_tools(
+        self,
+        tool_names: Sequence[str | FunctionRef | FunctionGroupRef],
+        wrapper_type: LLMFrameworkEnum | str,
+    ) -> list[typing.Any]:
         """Mock implementation."""
         return []
examples/object_store/user_report/configs/config_mysql.yml (1)

16-22: Remove duplicate object_store under front_end.

Keep only group-level object_store.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']
examples/object_store/user_report/configs/config_redis.yml (1)

16-22: Remove duplicate object_store under front_end.

Same rationale as other configs.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']
src/nat/data_models/config.py (1)

62-66: ‘workflow’ suggestions should include both functions and function groups.

Currently only functions are suggested.

-            if (info.field_name in ('workflow', 'functions')):
-                registered_keys = GlobalTypeRegistry.get().get_registered_functions()
+            if info.field_name == 'workflow':
+                registered_keys = (
+                    list(GlobalTypeRegistry.get().get_registered_functions())
+                    + list(GlobalTypeRegistry.get().get_registered_function_groups())
+                )
+            elif info.field_name == 'functions':
+                registered_keys = GlobalTypeRegistry.get().get_registered_functions()
-            elif (info.field_name == "function_groups"):
+            elif info.field_name == "function_groups":
                 registered_keys = GlobalTypeRegistry.get().get_registered_function_groups()
src/nat/tool/github_tools.py (2)

195-203: Encode labels as comma-separated string for GitHub issues filter.

GitHub expects labels as a CSV string, not a list.

         for issue in issues_list.filter_parameters:
-            params = issue.model_dump(exclude_unset=True, exclude_none=True)
+            params = issue.model_dump(exclude_unset=True, exclude_none=True)
+            if isinstance(params.get("labels"), list):
+                params["labels"] = ",".join(params["labels"])
             response = await client.get(url, params=params)

424-429: Fix raw.githubusercontent.com URL (remove refs/heads).

Current URL is invalid; include branch in file_path already.

-            # The following URL is the raw URL of the file. refs/heads/ always points to the top commit of the branch
-            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/refs/heads/{file_metadata.file_path}"
+            # Build correct raw GitHub URL: owner/repo/{branch}/{path}
+            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/{file_metadata.file_path}"
src/nat/builder/workflow_builder.py (1)

1157-1166: ChildBuilder.get_tools(): normalize keys before dependency tracking.

Without normalization, FunctionGroupRef won’t match builder keys and dependencies get misclassified.

-        for tool_name in tool_names:
-            if tool_name in self._workflow_builder._function_groups:
-                self._dependencies.add_function_group(tool_name)
-            else:
-                self._dependencies.add_function(tool_name)
+        for tool_name in tool_names:
+            key = str(tool_name)
+            if key in self._workflow_builder._function_groups:
+                self._dependencies.add_function_group(key)
+            else:
+                self._dependencies.add_function(key)
🧹 Nitpick comments (11)
src/nat/builder/function.py (1)

17-17: Precompile the name-validation regex once.

Avoid recompiling the pattern per call; define it at module scope and reuse.

Add near the imports (outside the changed hunk):

NAME_RE = re.compile(r"^[A-Za-z0-9_-]+\Z")
src/nat/data_models/function.py (1)

51-55: Add return type to model-level validator.

Minor typing polish per repo rules.

-    @model_validator(mode="after")
-    def validate_include_exclude(self):
+    @model_validator(mode="after")
+    def validate_include_exclude(self) -> "FunctionGroupBaseConfig":
         if self.include and self.exclude:
             raise ValueError("include and exclude cannot be used together")
         return self
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)

135-142: Return a spec’ed Function mock for consistency.

Current AsyncMock lacks spec; prefer spec to catch misuse.

-        if name in self._mocks:
-            mock_fn = AsyncMock()
-            mock_fn.ainvoke = AsyncMock(return_value=self._mocks[name])
-            return mock_fn
+        if name in self._mocks:
+            mock_fn = MagicMock(spec=Function)
+            mock_fn.ainvoke = AsyncMock(return_value=self._mocks[name])
+            return mock_fn
src/nat/builder/builder.py (1)

279-282: Consider widening dependency lookup to accept FunctionGroupRef.

Improves parity with other APIs and WorkflowBuilder.

-    def get_function_group_dependencies(self, fn_name: str) -> FunctionDependencies:
+    def get_function_group_dependencies(self, fn_name: str | FunctionGroupRef) -> FunctionDependencies:
         pass
src/nat/tool/github_tools.py (3)

437-447: Validate start/end ordering for line ranges.

Handle start > end explicitly.

-            selected_lines = lines[file_metadata.start_line - 1:file_metadata.end_line]
+            if file_metadata.start_line > file_metadata.end_line:
+                return (f"Error: Start line {file_metadata.start_line} is greater than "
+                        f"end line {file_metadata.end_line} for file {file_metadata.file_path}")
+            selected_lines = lines[file_metadata.start_line - 1:file_metadata.end_line]

166-181: Add GitHub API version header.

Improves compatibility across org policies.

     headers = {
         "Authorization": f"Bearer {token}",
         "Accept": "application/vnd.github+json",
-        "User-Agent": "NeMo-Agent-Toolkit",
+        "User-Agent": "NeMo-Agent-Toolkit",
+        "X-GitHub-Api-Version": "2022-11-28",
     }

151-160: Annotate async generators with return types.

Conform to repo typing rules.

-from typing import Literal
+from typing import Literal, AsyncIterator
@@
-@register_function_group(config_type=GithubGroupConfig)
-async def github_tool(config: GithubGroupConfig, _builder: Builder):
+@register_function_group(config_type=GithubGroupConfig)
+async def github_tool(config: GithubGroupConfig, _builder: Builder) -> AsyncIterator[FunctionGroup]:
@@
-@register_function(config_type=GithubFilesGroupConfig)
-async def github_files_tool(config: GithubFilesGroupConfig, _builder: Builder):
+@register_function(config_type=GithubFilesGroupConfig)
+async def github_files_tool(config: GithubFilesGroupConfig, _builder: Builder) -> AsyncIterator[FunctionInfo]:

Also applies to: 377-381

src/nat/builder/workflow_builder.py (2)

373-410: Seed dependencies under instance key, not config.type.

Storing at config.type then again at name leaves a stray entry and complicates lookups. Recommend seeding under name only.

-        self.current_function_group_building = config.type
-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        self.current_function_group_building = config.type
+        # Empty set of dependencies for the current function group (track by instance key)
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies

438-447: Avoid double lookup; cache included functions.

Micro-optimization and readability.

-        # If the function group exposes functions, record and add them to the registry
-        for k in build_result.instance.get_included_functions():
+        # If the function group exposes functions, record and add them to the registry
+        included = build_result.instance.get_included_functions()
+        for k in included:
             if k in self._functions:
                 raise ValueError(f"Exposed function `{k}` from group `{name}` conflicts with an existing function")
-        self._functions.update({
-            k: ConfiguredFunction(config=v.config, instance=v)
-            for k, v in build_result.instance.get_included_functions().items()
-        })
+        self._functions.update({k: ConfiguredFunction(config=v.config, instance=v) for k, v in included.items()})
tests/nat/builder/test_builder.py (2)

139-143: Docstring mismatch (“includes” vs “excludes”).

AllExcludesFunctionGroupConfig says “includes all functions.” Should say “excludes all functions.”

-class AllExcludesFunctionGroupConfig(FunctionGroupBaseConfig, name="all_excludes_function_group"):
-    """Test configuration that includes all functions."""
+class AllExcludesFunctionGroupConfig(FunctionGroupBaseConfig, name="all_excludes_function_group"):
+    """Test configuration that excludes all functions."""

1138-1165: Add a build()-path test to catch expose/include regression.

Currently no test calls build() with groups; the builder bug around exposed functions wouldn’t be caught. Add a test that adds a group with includes, builds, and asserts the included functions are not duplicated in config.functions.

I can draft a targeted test if you want.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06fa121 and aea7e1d.

📒 Files selected for processing (42)
  • ci/scripts/path_checks.py (1 hunks)
  • docs/source/extend/function-groups.md (1 hunks)
  • docs/source/index.md (2 hunks)
  • docs/source/workflows/function-groups.md (1 hunks)
  • examples/object_store/user_report/README.md (8 hunks)
  • examples/object_store/user_report/configs/config_mem.yml (1 hunks)
  • examples/object_store/user_report/configs/config_mysql.yml (1 hunks)
  • examples/object_store/user_report/configs/config_redis.yml (1 hunks)
  • examples/object_store/user_report/configs/config_s3.yml (1 hunks)
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py (3 hunks)
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5 hunks)
  • src/nat/agent/react_agent/register.py (2 hunks)
  • src/nat/agent/reasoning_agent/reasoning_agent.py (1 hunks)
  • src/nat/agent/rewoo_agent/register.py (2 hunks)
  • src/nat/agent/tool_calling_agent/register.py (2 hunks)
  • src/nat/builder/builder.py (5 hunks)
  • src/nat/builder/component_utils.py (4 hunks)
  • src/nat/builder/function.py (3 hunks)
  • src/nat/builder/workflow.py (5 hunks)
  • src/nat/builder/workflow_builder.py (17 hunks)
  • src/nat/cli/register_workflow.py (4 hunks)
  • src/nat/cli/type_registry.py (10 hunks)
  • src/nat/data_models/component.py (2 hunks)
  • src/nat/data_models/component_ref.py (1 hunks)
  • src/nat/data_models/config.py (10 hunks)
  • src/nat/data_models/function.py (2 hunks)
  • src/nat/data_models/function_dependencies.py (3 hunks)
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py (1 hunks)
  • src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py (1 hunks)
  • src/nat/profiler/utils.py (2 hunks)
  • src/nat/tool/github_tools.py (1 hunks)
  • src/nat/tool/github_tools/create_github_commit.py (0 hunks)
  • src/nat/tool/github_tools/create_github_issue.py (0 hunks)
  • src/nat/tool/github_tools/create_github_pr.py (0 hunks)
  • src/nat/tool/github_tools/get_github_file.py (0 hunks)
  • src/nat/tool/github_tools/get_github_issue.py (0 hunks)
  • src/nat/tool/github_tools/get_github_pr.py (0 hunks)
  • src/nat/tool/github_tools/update_github_issue.py (0 hunks)
  • src/nat/tool/register.py (1 hunks)
  • tests/nat/agent/test_reasoning_agent.py (3 hunks)
  • tests/nat/builder/test_builder.py (16 hunks)
  • tests/nat/front_ends/mcp/test_mcp_custom_routes.py (2 hunks)
💤 Files with no reviewable changes (7)
  • src/nat/tool/github_tools/create_github_issue.py
  • src/nat/tool/github_tools/create_github_commit.py
  • src/nat/tool/github_tools/create_github_pr.py
  • src/nat/tool/github_tools/update_github_issue.py
  • src/nat/tool/github_tools/get_github_issue.py
  • src/nat/tool/github_tools/get_github_pr.py
  • src/nat/tool/github_tools/get_github_file.py
✅ Files skipped from review due to trivial changes (1)
  • docs/source/workflows/function-groups.md
🚧 Files skipped from review as they are similar to previous changes (17)
  • src/nat/data_models/function_dependencies.py
  • src/nat/data_models/component.py
  • src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py
  • src/nat/agent/react_agent/register.py
  • src/nat/agent/tool_calling_agent/register.py
  • docs/source/index.md
  • docs/source/extend/function-groups.md
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • examples/object_store/user_report/configs/config_mem.yml
  • src/nat/data_models/component_ref.py
  • examples/object_store/user_report/README.md
  • src/nat/agent/rewoo_agent/register.py
  • src/nat/profiler/utils.py
  • src/nat/cli/register_workflow.py
  • tests/nat/front_ends/mcp/test_mcp_custom_routes.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • src/nat/builder/workflow.py
🧰 Additional context used
📓 Path-based instructions (14)
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • ci/scripts/path_checks.py
  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/config.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • ci/scripts/path_checks.py
  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • examples/object_store/user_report/configs/config_s3.yml
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • src/nat/data_models/config.py
  • examples/object_store/user_report/configs/config_redis.yml
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • ci/scripts/path_checks.py
  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/config.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • ci/scripts/path_checks.py
  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • examples/object_store/user_report/configs/config_s3.yml
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • src/nat/data_models/config.py
  • examples/object_store/user_report/configs/config_redis.yml
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • ci/scripts/path_checks.py
  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • examples/object_store/user_report/configs/config_s3.yml
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • src/nat/data_models/config.py
  • examples/object_store/user_report/configs/config_redis.yml
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • tests/nat/agent/test_reasoning_agent.py
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/tool/register.py
  • src/nat/tool/github_tools.py
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
**/configs/**/*.y?(a)ml

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Configuration files consumed by code must live in a neighbouring configs/ directory

Files:

  • examples/object_store/user_report/configs/config_s3.yml
  • examples/object_store/user_report/configs/config_mysql.yml
  • examples/object_store/user_report/configs/config_redis.yml
**/*.{yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

In workflow/config YAML, set llms.._type: nat_test_llm to stub responses.

Files:

  • examples/object_store/user_report/configs/config_s3.yml
  • examples/object_store/user_report/configs/config_mysql.yml
  • examples/object_store/user_report/configs/config_redis.yml
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/object_store/user_report/configs/config_s3.yml
  • examples/object_store/user_report/configs/config_mysql.yml
  • examples/object_store/user_report/configs/config_redis.yml
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Unit tests must live under tests/ and use configured markers (e2e, integration, etc.)

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py

⚙️ CodeRabbit configuration file

tests/**/*.py: - Ensure that tests are comprehensive, cover edge cases, and validate the functionality of the code. - Test functions should be named using the test_ prefix, using snake_case. - Any frequently repeated code should be extracted into pytest fixtures. - Pytest fixtures should define the name argument when applying the pytest.fixture decorator. The fixture
function being decorated should be named using the fixture_ prefix, using snake_case. Example:
@pytest.fixture(name="my_fixture")
def fixture_my_fixture():
pass

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
**/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/tests/**/*.py: Test functions must use the test_ prefix and snake_case
Extract repeated test code into pytest fixtures; fixtures should set name=... in @pytest.fixture and functions named with fixture_ prefix
Mark expensive tests with @pytest.mark.slow or @pytest.mark.integration
Use pytest with pytest-asyncio for async code; mock external services with pytest_httpserver or unittest.mock

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
packages/*/src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

packages/*/src/**/*.py: All importable Python code in packages must live under packages//src/
All public APIs in packaged code require Python 3.11+ type hints; prefer typing/collections.abc; use typing.Annotated when useful
Provide Google-style docstrings for public APIs in packages; first line concise with a period; use backticks for code entities

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
packages/**/*

⚙️ CodeRabbit configuration file

packages/**/*: - This directory contains optional plugin packages for the toolkit, each should contain a pyproject.toml file. - The pyproject.toml file should declare a dependency on nvidia-nat or another package with a name starting
with nvidia-nat-. This dependency should be declared using ~=<version>, and the version should be a two
digit version (ex: ~=1.0).

  • Not all packages contain Python code, if they do they should also contain their own set of tests, in a
    tests/ directory at the same level as the pyproject.toml file.

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
🧠 Learnings (9)
📓 Common learnings
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.
📚 Learning: 2025-08-28T20:03:37.650Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.

Applied to files:

  • src/nat/builder/component_utils.py
  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are separate class hierarchies that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other.

Applied to files:

  • src/nat/builder/component_utils.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are sibling classes that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other and the order doesn't matter for classification purposes.

Applied to files:

  • src/nat/builder/component_utils.py
  • src/nat/cli/type_registry.py
  • src/nat/builder/builder.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:30:12.178Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.

Applied to files:

  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.

Applied to files:

  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.

Applied to files:

  • src/nat/builder/function.py
  • tests/nat/builder/test_builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:29:34.897Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/workflow_builder.py:373-410
Timestamp: 2025-08-28T14:29:34.897Z
Learning: ComponentRef objects (like FunctionGroupRef) automatically convert to strings when used as dictionary keys in Python, so explicit str() conversions are not required when using them as dict keys.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T23:22:41.742Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-08-28T23:22:41.742Z
Learning: Applies to **/*.py : When re-raising exceptions, use bare `raise` and log with logger.error(), not logger.exception()

Applied to files:

  • src/nat/builder/workflow_builder.py
🧬 Code graph analysis (13)
src/nat/tool/register.py (1)
src/nat/tool/github_tools.py (2)
  • github_tool (152-370)
  • github_files_tool (378-450)
src/nat/tool/github_tools.py (4)
src/nat/builder/builder.py (1)
  • Builder (64-281)
src/nat/builder/function.py (2)
  • FunctionGroup (350-526)
  • add_function (371-411)
src/nat/data_models/function.py (2)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (2)
  • add_function (412-423)
  • add_function (1111-1112)
src/nat/agent/reasoning_agent/reasoning_agent.py (3)
examples/object_store/user_report/tests/test_user_report_tool.py (1)
  • builder (33-49)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)
  • get_function_dependencies (284-286)
  • get_function_group_dependencies (288-290)
src/nat/builder/workflow_builder.py (4)
  • get_function_dependencies (514-517)
  • get_function_dependencies (1296-1297)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
src/nat/builder/component_utils.py (2)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/data_models/component.py (1)
  • ComponentGroup (46-56)
src/nat/builder/function.py (2)
src/nat/data_models/function.py (3)
  • EmptyFunctionConfig (58-59)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/context.py (1)
  • Context (93-277)
src/nat/cli/type_registry.py (8)
src/nat/builder/function.py (1)
  • FunctionGroup (350-526)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/builder.py (2)
  • Builder (64-281)
  • get_function_group (79-80)
src/nat/cli/register_workflow.py (1)
  • register_function_group (180-211)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)
  • get_function_group (151-157)
src/nat/builder/workflow_builder.py (2)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
src/nat/data_models/component.py (1)
  • ComponentEnum (22-43)
src/nat/data_models/common.py (1)
  • static_type (154-155)
src/nat/builder/builder.py (6)
src/nat/builder/function.py (2)
  • FunctionGroup (350-526)
  • Function (45-300)
src/nat/data_models/component_ref.py (2)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (7)
  • add_function_group (147-149)
  • get_function (135-141)
  • get_function_group (151-157)
  • get_function_config (143-145)
  • get_function_group_config (159-161)
  • get_tools (179-181)
  • get_function_group_dependencies (288-290)
src/nat/builder/workflow_builder.py (14)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function (451-457)
  • get_function (1119-1125)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_config (469-475)
  • get_function_config (1137-1138)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_tools (526-559)
  • get_tools (1157-1166)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
src/nat/data_models/function_dependencies.py (2)
  • add_function_group (64-65)
  • FunctionDependencies (21-80)
tests/nat/builder/test_builder.py (6)
src/nat/builder/builder.py (6)
  • Builder (64-281)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function_group (79-80)
  • get_function_group_config (94-95)
  • get_function (75-76)
src/nat/builder/function.py (8)
  • Function (45-300)
  • FunctionGroup (350-526)
  • add_function (371-411)
  • get_accessible_functions (441-463)
  • get_all_functions (514-526)
  • get_included_functions (488-512)
  • get_excluded_functions (465-486)
  • get_config (413-422)
src/nat/cli/register_workflow.py (2)
  • register_function_group (180-211)
  • register_function (148-177)
src/nat/cli/type_registry.py (4)
  • register_function_group (499-515)
  • register_function (477-485)
  • get_function_group (517-533)
  • get_function (487-493)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (12)
  • add_function (412-423)
  • add_function (1111-1112)
  • WorkflowBuilder (141-1095)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_function (451-457)
  • get_function (1119-1125)
  • dependencies (1107-1108)
src/nat/data_models/config.py (3)
src/nat/data_models/function.py (3)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
  • EmptyFunctionConfig (58-59)
src/nat/cli/type_registry.py (3)
  • get (1049-1050)
  • get_registered_function_groups (535-541)
  • compute_annotation (997-1041)
src/nat/data_models/common.py (2)
  • TypedBaseModel (93-168)
  • discriminator (162-168)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5)
src/nat/builder/builder.py (6)
  • Builder (64-281)
  • add_function_group (71-72)
  • get_function_group (79-80)
  • get_function_group_config (94-95)
  • get_tools (110-113)
  • get_function_group_dependencies (280-281)
src/nat/builder/function.py (2)
  • Function (45-300)
  • FunctionGroup (350-526)
src/nat/data_models/function.py (2)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (10)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_tools (526-559)
  • get_tools (1157-1166)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
src/nat/data_models/function_dependencies.py (2)
  • add_function_group (64-65)
  • FunctionDependencies (21-80)
src/nat/builder/workflow_builder.py (9)
src/nat/builder/function.py (5)
  • FunctionGroup (350-526)
  • add_function (371-411)
  • Function (45-300)
  • get_included_functions (488-512)
  • get_accessible_functions (441-463)
src/nat/data_models/component_ref.py (12)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
  • component_group (69-76)
  • component_group (90-91)
  • component_group (101-102)
  • component_group (112-113)
  • component_group (123-124)
  • component_group (134-135)
  • component_group (145-146)
  • component_group (156-157)
  • component_group (167-168)
  • component_group (178-179)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/data_models/function_dependencies.py (5)
  • FunctionDependencies (21-80)
  • add_function (61-62)
  • add_function_group (64-65)
  • add_object_store (76-77)
  • add_retriever (79-80)
src/nat/data_models/config.py (1)
  • Config (238-423)
src/nat/builder/workflow.py (2)
  • Workflow (41-160)
  • from_entry_fn (128-160)
src/nat/builder/builder.py (11)
  • get_function_group (79-80)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function (75-76)
  • get_function_config (90-91)
  • get_function_group_config (94-95)
  • get_function_group_dependencies (280-281)
  • get_tools (110-113)
  • get_tool (116-117)
  • add_object_store (157-158)
  • add_retriever (217-218)
src/nat/profiler/decorators/framework_wrapper.py (1)
  • chain_wrapped_build_fn (108-128)
src/nat/data_models/component.py (1)
  • ComponentGroup (46-56)
src/nat/data_models/function.py (1)
src/nat/data_models/common.py (2)
  • TypedBaseModel (93-168)
  • BaseModelRegistryTag (88-90)
tests/nat/agent/test_reasoning_agent.py (3)
examples/object_store/user_report/tests/test_user_report_tool.py (1)
  • builder (33-49)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)
  • get_function_dependencies (284-286)
  • get_function_group_dependencies (288-290)
src/nat/builder/workflow_builder.py (4)
  • get_function_dependencies (514-517)
  • get_function_dependencies (1296-1297)
  • get_function_group_dependencies (520-523)
  • get_function_group_dependencies (1300-1301)
🪛 Ruff (0.12.2)
src/nat/tool/github_tools.py

66-66: Avoid specifying long messages outside the exception class

(TRY003)


173-174: Avoid specifying long messages outside the exception class

(TRY003)


266-266: Avoid specifying long messages outside the exception class

(TRY003)


279-279: Avoid specifying long messages outside the exception class

(TRY003)


281-281: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/builder/function.py

402-402: Avoid specifying long messages outside the exception class

(TRY003)


404-404: Avoid specifying long messages outside the exception class

(TRY003)


406-406: Avoid specifying long messages outside the exception class

(TRY003)


432-433: Avoid specifying long messages outside the exception class

(TRY003)


481-482: Avoid specifying long messages outside the exception class

(TRY003)


506-508: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/cli/type_registry.py

509-511: Avoid specifying long messages outside the exception class

(TRY003)


532-533: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/builder/test_builder.py

292-292: Avoid specifying long messages outside the exception class

(TRY003)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Avoid specifying long messages outside the exception class

(TRY003)


363-363: Avoid specifying long messages outside the exception class

(TRY003)


390-390: Avoid specifying long messages outside the exception class

(TRY003)


413-413: Unused function argument: config

(ARG001)


417-417: Avoid specifying long messages outside the exception class

(TRY003)

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py

157-157: Avoid specifying long messages outside the exception class

(TRY003)


159-159: Unused method argument: name

(ARG002)


179-179: Unused method argument: tool_names

(ARG002)


179-179: Unused method argument: wrapper_type

(ARG002)


288-288: Unused method argument: fn_name

(ARG002)

src/nat/builder/workflow_builder.py

406-407: Prefer TypeError exception for invalid type

(TRY004)


406-407: Avoid specifying long messages outside the exception class

(TRY003)


417-417: Avoid specifying long messages outside the exception class

(TRY003)


431-431: Avoid specifying long messages outside the exception class

(TRY003)


442-442: Avoid specifying long messages outside the exception class

(TRY003)


455-455: Avoid specifying long messages outside the exception class

(TRY003)


464-464: Avoid specifying long messages outside the exception class

(TRY003)


473-473: Avoid specifying long messages outside the exception class

(TRY003)


482-482: Avoid specifying long messages outside the exception class

(TRY003)


536-536: Avoid specifying long messages outside the exception class

(TRY003)


541-541: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/data_models/function.py

48-48: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/agent/test_reasoning_agent.py

141-141: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


142-142: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


305-305: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


306-306: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


380-380: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


381-381: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

🔇 Additional comments (20)
ci/scripts/path_checks.py (1)

107-107: Allowlist term addition looks good.

"delete/recreate" is consistent with existing literal/regex entries and will be correctly ignored by ALLOWLISTED_WORDS_REGEX.

src/nat/builder/function.py (1)

514-526: LGTM: preserves insertion order and returns fully qualified names.

src/nat/agent/reasoning_agent/reasoning_agent.py (1)

116-121: LGTM: safe merge of function and group deps without mutating builder state.

Creating a fresh set and updating per group fixes the prior aliasing issue.

src/nat/tool/register.py (1)

27-28: Consolidated GitHub tool exports look good.

Importing github_tool and github_files_tool aligns with the Function Groups refactor and preserves auto-registration via the module import above.

src/nat/builder/component_utils.py (3)

33-33: Correct: add FunctionGroupBaseConfig import for grouping support.


52-53: Right order: process function groups before functions.

Ensures group-scoped deps/contexts are available before individual functions.


112-113: Mapping for FunctionGroup configs is correct.

Order with FunctionBaseConfig is fine since these base classes are siblings (no misclassification risk per current hierarchy).

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

30-43: Solid include/exclude design; uniqueness check is correct.

Clean separation from FunctionBaseConfig and clear erroring on duplicates. LGTM.

Also applies to: 44-50

src/nat/builder/builder.py (1)

109-113: API surface for group-aware get_tools looks good.

Signature aligns with FunctionRef/FunctionGroupRef usage.

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

239-268: Nice: default_factory for dicts, print_summary, and annotations for function_groups.

Good defensive defaults and visibility.

Also applies to: 286-288, 339-341, 384-388

src/nat/tool/github_tools.py (1)

263-339: Commit flow looks correct and hardened.

Good path traversal guard, base64 blobs, parent/tree/commit/ref sequence, and error handling.

src/nat/cli/type_registry.py (7)

91-114: FunctionGroup callable type aliases look good.

The added type aliases align with the existing Function callables and keep symmetry across the API surface.


186-194: RegisteredFunctionGroupInfo is well-scoped and documented.

Clear docstring, correct type parameters, and consistent with the other Registered* containers.


331-333: State slot for function groups added correctly.

Initialization mirrors other component registries; no issues spotted.


499-541: Add/get/list API for function groups integrates cleanly.

Error messaging matches style of adjacent registrars; _registration_changed hook is invoked. LGTM.


855-857: get_infos_by_type() extension for FUNCTION_GROUP is correct.

Return type aligns with other component branches.


922-924: Minor verification request: static_type access on dict keys.

You’re iterating dict keys (types) and calling i.static_type(). If static_type is not a classmethod on those types (and instead a utility function), this will fail at runtime. Please confirm it’s a classmethod on all config types.


1014-1016: compute_annotation() support for FunctionGroupBaseConfig is correct.

Delegation mirrors the Function branch; no concerns.

src/nat/builder/workflow_builder.py (1)

243-249: Bug: build() uses removed expose field; duplicates/AttributeError risk.

FunctionGroupBaseConfig now uses include/exclude; accessing v.config.expose will fail. Also, exposed (globally addressable) functions should come from each group’s included set.

Apply:

-        # Get all functions which are exposed by function groups
-        exposed_functions = {f"{k}.{n}" for k, v in self._function_groups.items() for n in v.config.expose}
+        # Functions globally exposed by function groups = union of each group's included functions
+        exposed_functions = (
+            set().union(*(g.instance.get_included_functions().keys() for g in self._function_groups.values()))
+            if self._function_groups else set()
+        )
@@
-        # Expose functions that are not in a function group
-        function_instances = {k: v.instance for k, v in self._functions.items() if k not in exposed_functions}
-        # Expose all function groups
-        function_group_instances = {k: v.instance for k, v in self._function_groups.items()}
+        # Expose functions that are not in a function group
+        function_instances = {k: v.instance for k, v in self._functions.items() if k not in exposed_functions}
+        # Expose all function groups
+        function_group_instances = {k: v.instance for k, v in self._function_groups.items()}

Also applies to: 284-287

⛔ Skipped due to learnings
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.
tests/nat/builder/test_builder.py (1)

114-148: Config defaults and registrations look correct.

Good use of Field(default_factory=...) and clear function-group coverage.

@willkill07 willkill07 requested a review from a team as a code owner September 15, 2025 21:17
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: 5

Caution

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

⚠️ Outside diff range comments (2)
src/nat/builder/workflow_builder.py (1)

486-496: Broaden set_workflow to accept FunctionGroup configs (API parity).

Matches config/workflow annotation and Builder interface.

-    async def set_workflow(self, config: FunctionBaseConfig) -> Function:
+    async def set_workflow(self, config: FunctionBaseConfig | FunctionGroupBaseConfig) -> Function:
tests/nat/builder/test_builder.py (1)

891-934: Fix variable shadowing; current assertion is a tautology.

workflow_config is reassigned to workflow.config, making assert workflow_config.workflow == workflow_config.workflow always true. Use distinct names.

-async def test_built_config():
+async def test_built_config():
@@
-    workflow_config = FunctionReturningFunctionConfig()
+    workflow_cfg = FunctionReturningFunctionConfig()
@@
-        await builder.set_workflow(workflow_config)
+        await builder.set_workflow(workflow_cfg)
@@
-        workflow_config = workflow.config
+        built_cfg = workflow.config
@@
-        assert workflow_config.general == general_config
-        assert workflow_config.functions == {"function1": function_config}
-        assert workflow_config.workflow == workflow_config.workflow
-        assert workflow_config.llms == {"llm1": llm_config}
-        assert workflow_config.embedders == {"embedder1": embedder_config}
-        assert workflow_config.memory == {"memory1": memory_config}
-        assert workflow_config.retrievers == {"retriever1": retriever_config}
-        assert workflow_config.object_stores == {"object_store1": object_store_config}
-        assert workflow_config.ttc_strategies == {"ttc_strategy": ttc_config}
+        assert built_cfg.general == general_config
+        assert built_cfg.functions == {"function1": function_config}
+        assert built_cfg.workflow == workflow_cfg
+        assert built_cfg.llms == {"llm1": llm_config}
+        assert built_cfg.embedders == {"embedder1": embedder_config}
+        assert built_cfg.memory == {"memory1": memory_config}
+        assert built_cfg.retrievers == {"retriever1": retriever_config}
+        assert built_cfg.object_stores == {"object_store1": object_store_config}
+        assert built_cfg.ttc_strategies == {"ttc_strategy": ttc_config}
♻️ Duplicate comments (21)
src/nat/data_models/function_dependencies.py (1)

37-39: Deterministic JSON: sort function_groups during serialization.

Repeat of a prior suggestion: converting a set to a list yields nondeterministic order; sort for stable outputs and easier testing.

-    def serialize_function_groups(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_function_groups(self, v: set[str]) -> list[str]:
+        return sorted(v)
src/nat/profiler/utils.py (1)

36-38: Signature update LGTM; align doc/log text to function groups and support Enum values.

Great to accept RegisteredFunctionGroupInfo. Also:

  • Adjust the warning to mention @register_function_group.
  • Accept enum instances in framework_wrappers to be robust (common in internal code).

[suggest_minor_issue]

-    if registration.framework_wrappers:
-        results: list[LLMFrameworkEnum] = []
-        for fw_str in registration.framework_wrappers:
-            try:
-                results.append(LLMFrameworkEnum(fw_str))
-            except ValueError:
-                # If it's not recognized, ignore or log
-                logger.warning("Unrecognized framework %s in registration.framework_wrappers", fw_str)
+    if registration.framework_wrappers:
+        results: list[LLMFrameworkEnum] = []
+        for fw in registration.framework_wrappers:
+            try:
+                results.append(fw if isinstance(fw, LLMFrameworkEnum) else LLMFrameworkEnum(fw))
+            except ValueError:
+                logger.warning("Unrecognized framework %s in registration.framework_wrappers", fw)
         return list(set(results))  # unique

And update the guidance below:

-            "types in the framework_wrappers argument when calling @register_function.",
+            "types in the framework_wrappers argument when calling @register_function or @register_function_group.",
src/nat/builder/component_utils.py (2)

112-113: Classification for FunctionGroupBaseConfig — LGTM.

Given FunctionGroupBaseConfig and FunctionBaseConfig are siblings, current order is fine per prior discussion.


261-263: Backward compatibility: guard missing attrs in older configs.

Direct access to config.function_groups can break old configs; mirror the tolerant pattern with getattr(..., {}).

-    total_node_count = (len(config.embedders) + len(config.functions) + len(config.function_groups) + len(config.llms) +
-                        len(config.memory) + len(config.object_stores) + len(config.retrievers) +
-                        len(config.ttc_strategies) + len(config.authentication) + 1)  # +1 for the workflow
+    total_node_count = (
+        len(getattr(config, "embedders", {}))
+        + len(getattr(config, "functions", {}))
+        + len(getattr(config, "function_groups", {}))
+        + len(getattr(config, "llms", {}))
+        + len(getattr(config, "memory", {}))
+        + len(getattr(config, "object_stores", {}))
+        + len(getattr(config, "retrievers", {}))
+        + len(getattr(config, "ttc_strategies", {}))
+        + len(getattr(config, "authentication", {}))
+        + 1  # +1 for the workflow
+    )
#!/bin/bash
# Quick grep to spot any remaining direct attribute reads that could break older configs
rg -nP --type=py 'config\.(functions|function_groups|embedders|llms|memory|object_stores|retrievers|ttc_strategies|authentication)\b' -C2
examples/object_store/user_report/README.md (3)

124-128: Fix broken docker‑compose MinIO link (missing examples/ path segment).

-You can use the [docker-compose.minio.yml](../../deploy/docker-compose.minio.yml) file to start a MinIO server in a local docker container.
+You can use the [docker-compose.minio.yml](../../examples/deploy/docker-compose.minio.yml) file to start a MinIO server in a local docker container.

135-147: Fix broken docker‑compose MySQL link (missing examples/ path segment).

-If you want to use a MySQL server, you can use the [docker-compose.mysql.yml](../../deploy/docker-compose.mysql.yml) file to start a MySQL server in a local docker container.
+If you want to use a MySQL server, you can use the [docker-compose.mysql.yml](../../examples/deploy/docker-compose.mysql.yml) file to start a MySQL server in a local docker container.

154-158: Fix broken docker‑compose Redis link (missing examples/ path segment).

-If you want to use a Redis server, you can use the [docker-compose.redis.yml](../../deploy/docker-compose.redis.yml) file to start a Redis server in a local docker container.
+If you want to use a Redis server, you can use the [docker-compose.redis.yml](../../examples/deploy/docker-compose.redis.yml) file to start a Redis server in a local docker container.
examples/object_store/user_report/configs/config_s3.yml (1)

16-21: Remove duplicate object_store at front_end.

Group-level object_store under function_groups.user_report is the source of truth; keep only one to avoid schema validation issues.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']
tests/nat/agent/test_reasoning_agent.py (3)

141-146: Annotate class attributes as ClassVar (RUF012).

-    class FakeDeps:
-        functions = {"SomeTool"}
-        function_groups = set()
+    from typing import ClassVar
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"SomeTool"}
+        function_groups: ClassVar[set[str]] = set()

305-310: Repeat ClassVar annotations here.

-    class FakeDeps:
-        functions = {"ToolA", "ToolB"}
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"ToolA", "ToolB"}
+        function_groups: ClassVar[set[str]] = set()

380-385: Repeat ClassVar annotations for empty-set FakeDeps.

-    class FakeDeps:
-        functions = set()
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = set()
+        function_groups: ClassVar[set[str]] = set()
docs/source/workflows/function-groups.md (1)

52-53: Fix Builder import path in snippet.

-from nat.builder.workflow_builder import Builder
+from nat.builder.builder import Builder
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (4)

147-150: Align add_function_group signature with abstract Builder.

-    async def add_function_group(self, name: str, config: FunctionGroupBaseConfig) -> FunctionGroup:
+    async def add_function_group(self, name: str | FunctionGroupRef, config: FunctionGroupBaseConfig) -> FunctionGroup:

Additional imports required:

# at top-level imports
from nat.data_models.component_ref import FunctionRef, FunctionGroupRef
from nat.builder.framework_enum import LLMFrameworkEnum

151-158: Widen get_function_group signature; handle refs safely.

-    def get_function_group(self, name: str) -> FunctionGroup:
+    def get_function_group(self, name: str | FunctionGroupRef) -> FunctionGroup:
         """Return a mock function group if one is configured."""
-        if name in self._mocks:
-            mock_fn_group = MagicMock(spec=FunctionGroup)
-            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[name])
+        key = str(name)
+        if key in self._mocks:
+            mock_fn_group = MagicMock(spec=FunctionGroup)
+            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[key])
             return mock_fn_group
         raise ValueError(f"Function group '{name}' not mocked. Use mock_function_group() to add it.")

159-162: Match abstract get_function_group_config signature.

-    def get_function_group_config(self, name: str) -> FunctionGroupBaseConfig:
+    def get_function_group_config(self, name: str | FunctionGroupRef) -> FunctionGroupBaseConfig:
         """Mock implementation."""
         return FunctionGroupBaseConfig()

179-182: Match Builder.get_tools signature and types; silence ARG002.

-    def get_tools(self, tool_names: Sequence[str], wrapper_type):
-        """Mock implementation."""
-        return []
+    def get_tools(
+        self,
+        tool_names: Sequence[str | FunctionRef | FunctionGroupRef],
+        wrapper_type: LLMFrameworkEnum | str,
+    ) -> list[typing.Any]:
+        """Mock implementation."""
+        _ = tool_names; _ = wrapper_type
+        return []
examples/object_store/user_report/configs/config_mysql.yml (1)

16-21: Remove duplicate object_store at front_end.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']
examples/object_store/user_report/src/nat_user_report/user_report_tools.py (1)

46-47: Annotate function-group builder to return AsyncIterator[FunctionGroup].

Conforms to repo typing rules for public APIs.

 @register_function_group(config_type=UserReportConfig)
-async def user_report_group(config: UserReportConfig, builder: Builder):
+async def user_report_group(config: UserReportConfig, builder: Builder) -> "AsyncIterator[FunctionGroup]":
     """Register a function group for user report operations.

Add import:

# top of file
from typing import AsyncIterator
src/nat/builder/function.py (1)

371-378: Public API: add explicit return type and tighten converter typing.

Complies with repo typing rules and improves clarity.

-    def add_function(self,
+    def add_function(self,
                      name: str,
                      fn: Callable,
                      *,
                      input_schema: type[BaseModel] | None = None,
                      description: str | None = None,
-                     converters: list[Callable] | None = None):
+                     converters: list[Callable[[typing.Any], typing.Any]] | None = None) -> None:
src/nat/data_models/config.py (1)

61-66: Workflow suggestions must include function groups.

Returning only functions for workflow breaks UX and the PR intent.

-            if (info.field_name in ('workflow', 'functions')):
-                registered_keys = GlobalTypeRegistry.get().get_registered_functions()
-            elif (info.field_name == "function_groups"):
-                registered_keys = GlobalTypeRegistry.get().get_registered_function_groups()
+            if info.field_name == 'workflow':
+                registered_keys = (
+                    list(GlobalTypeRegistry.get().get_registered_functions())
+                    + list(GlobalTypeRegistry.get().get_registered_function_groups())
+                )
+            elif info.field_name == 'functions':
+                registered_keys = GlobalTypeRegistry.get().get_registered_functions()
+            elif info.field_name == 'function_groups':
+                registered_keys = GlobalTypeRegistry.get().get_registered_function_groups()
src/nat/tool/github_tools.py (1)

424-431: Fix raw.githubusercontent.com URL (remove refs/heads).

Current URL 404s; correct format is owner/repo/branch/path.

-            # The following URL is the raw URL of the file. refs/heads/ always points to the top commit of the branch
-            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/refs/heads/{file_metadata.file_path}"
+            # Build raw URL: owner/repo/branch/path
+            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/{file_metadata.file_path}"
🧹 Nitpick comments (22)
src/nat/data_models/component_ref.py (1)

105-114: Add return type for the overridden property (consistency with abstract signature).

The abstract component_group is annotated to return ComponentGroup; mirror that here for clarity and pyright.

     @property
     @override
-    def component_group(self):
+    def component_group(self) -> ComponentGroup:
         return ComponentGroup.FUNCTION_GROUPS
src/nat/data_models/function_dependencies.py (2)

33-59: Apply the same deterministic sort to all set serializers.

Keep JSON stable across runs by sorting every set serializer, not only function_groups.

-    def serialize_functions(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_functions(self, v: set[str]) -> list[str]:
+        return sorted(v)

-    def serialize_llms(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_llms(self, v: set[str]) -> list[str]:
+        return sorted(v)

-    def serialize_embedders(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_embedders(self, v: set[str]) -> list[str]:
+        return sorted(v)

-    def serialize_memory_clients(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_memory_clients(self, v: set[str]) -> list[str]:
+        return sorted(v)

-    def serialize_object_stores(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_object_stores(self, v: set[str]) -> list[str]:
+        return sorted(v)

-    def serialize_retrievers(self, v: set[str]) -> list[str]:
-        return list(v)
+    def serialize_retrievers(self, v: set[str]) -> list[str]:
+        return sorted(v)

64-66: Type‑hint return and drop unnecessary pylint disable.

Methods should be fully typed; the attribute exists so the pylint suppression is unnecessary.

-    def add_function_group(self, function_group: str):
-        self.function_groups.add(function_group)  # pylint: disable=no-member
+    def add_function_group(self, function_group: str) -> None:
+        self.function_groups.add(function_group)
examples/object_store/user_report/README.md (2)

53-71: Optional: clarify order semantics.

If the config sorts include/exclude, add a note that exposure order is alphabetical for determinism.

-**Naming Convention**: Functions are referenced as `user_report.get`, `user_report.put`, etc.
+**Naming Convention**: Functions are referenced as `user_report.get`, `user_report.put`, etc.
+**Order**: When using `include`/`exclude`, functions are expanded in deterministic (alphabetical) order.

20-21: Grammar nit: “An example tool …”

-And example tool in the NeMo Agent toolkit that makes use of an Object Store to retrieve data.
+An example tool in the NeMo Agent toolkit that makes use of an Object Store to retrieve data.
examples/object_store/user_report/configs/config_s3.yml (1)

31-36: Use nat_test_llm in examples to follow repo YAML guidance.

Stubs make examples deterministic and CI-friendly.

 llms:
   nim_llm:
-    _type: nim
-    model_name: meta/llama-3.1-70b-instruct
-    temperature: 0.0
+    _type: nat_test_llm
+    response_seq: []
+    delay_ms: 0
src/nat/data_models/function.py (1)

47-55: Shorten error messages or define exception class (TRY003).

Ruff TRY003 suggests shorter messages outside custom exceptions.

-            raise ValueError("Function names must be unique")
+            raise ValueError("Names must be unique")
@@
-            raise ValueError("include and exclude cannot be used together")
+            raise ValueError("Cannot set both include and exclude")
docs/source/workflows/function-groups.md (2)

108-119: Make include/exclude options mutually exclusive in YAML example.

Current snippet shows both; the model forbids using them together.

   math:
     _type: math_group
     # Option A: include and exclude no functions globally, but still usable as a group
     # # omit include and exclude lists

     # Option B: exclude selected functions globally
-    exclude: [mul]
+    # exclude: [mul]

     # Option C: include selected functions globally (accessible as a function named math.add, math.mul)
-    include: [add, mul]
+    include: [add, mul]

48-56: Annotate example builder with AsyncIterator return type.

-from pydantic import Field
+from pydantic import Field
+from typing import AsyncIterator
@@
-@register_function_group(config_type=MathGroupConfig)
-async def build_math_group(config: MathGroupConfig, _builder: Builder):
+@register_function_group(config_type=MathGroupConfig)
+async def build_math_group(config: MathGroupConfig, _builder: Builder) -> AsyncIterator[FunctionGroup]:

Also applies to: 72-96

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)

288-291: Use fn_name to avoid ARG002 (or explicitly discard).

-    def get_function_group_dependencies(self, fn_name: str) -> FunctionDependencies:
-        """Mock implementation."""
-        return FunctionDependencies()
+    def get_function_group_dependencies(self, fn_name: str) -> FunctionDependencies:
+        """Mock implementation."""
+        _ = fn_name
+        return FunctionDependencies()
src/nat/builder/builder.py (1)

279-282: Widen get_function_group_dependencies to accept refs.

Keeps parity with other APIs (get_function_group, get_tools).

-    def get_function_group_dependencies(self, fn_name: str) -> FunctionDependencies:
+    def get_function_group_dependencies(self, fn_name: str | FunctionGroupRef) -> FunctionDependencies:
         pass
examples/object_store/user_report/configs/config_mysql.yml (1)

32-36: Use nat_test_llm in examples to follow repo YAML guidance.

 llms:
   nim_llm:
-    _type: nim
-    model_name: meta/llama-3.1-70b-instruct
-    temperature: 0.0
+    _type: nat_test_llm
+    response_seq: []
+    delay_ms: 0
src/nat/builder/function.py (1)

401-406: Shorten exception messages and precompile the name regex (fixes TRY003).

Concise errors and a compiled pattern avoid ruff warnings and repeated compilation.

+NAME_RE = re.compile(r"^[A-Za-z0-9_-]+$")
@@
-        if not name.strip():
-            raise ValueError("Function name cannot be empty or blank")
-        if not re.match(r"^[a-zA-Z0-9_-]+$", name):
-            raise ValueError(f"Function name can only contain letters, numbers, underscores, and hyphens: {name}")
-        if name in self._functions:
-            raise ValueError(f"Function {name} already exists in function group {self._instance_name}")
+        name = name.strip()
+        if not name:
+            raise ValueError("Empty function name.")
+        if not NAME_RE.match(name):
+            raise ValueError(f"Invalid function name: {name!r}.")
+        if name in self._functions:
+            raise ValueError(f"Duplicate function: {name!r}.")

Add near the imports (after import re):

+NAME_RE = re.compile(r"^[A-Za-z0-9_-]+$")
src/nat/tool/github_tools.py (2)

176-181: Add recommended GitHub API version header.

Improves compatibility with the GitHub API.

     headers = {
         "Authorization": f"Bearer {token}",
         "Accept": "application/vnd.github+json",
         "User-Agent": "NeMo-Agent-Toolkit",
+        "X-GitHub-Api-Version": "2022-11-28",
     }

195-204: Encode labels as comma-separated string in GET /issues.

GitHub expects labels in a single comma-separated param.

         for issue in issues_list.filter_parameters:
             params = issue.model_dump(exclude_unset=True, exclude_none=True)
+            if isinstance(params.get("labels"), list):
+                params["labels"] = ",".join(params["labels"])
             response = await client.get(url, params=params)
src/nat/builder/workflow_builder.py (1)

396-404: Normalize function-group dependency key (use instance name consistently).

Avoids stray entries under config.type.

-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        # Empty set of dependencies for the current function group
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies
tests/nat/builder/test_builder.py (6)

114-148: Config defaults done right; fix a misleading docstring.

Field(default_factory=...) avoids mutable-default pitfalls. One nit: the AllExcludesFunctionGroupConfig docstring says “includes all functions” but this config excludes all.

Apply this diff to correct the docstring:

 class AllExcludesFunctionGroupConfig(FunctionGroupBaseConfig, name="all_excludes_function_group"):
-    """Test configuration that includes all functions."""
+    """Test configuration that excludes all functions."""

175-189: Add missing type hints in test helper class.

Keep tests type-complete per guidelines.

Apply this diff:

 class DerivedFunction(Function[str, str, str]):
@@
-    def some_method(self, val):
+    def some_method(self, val: str) -> str:
         return "some_method" + val
@@
-    async def _astream(self, value: str):
+    async def _astream(self, value: str):
         yield value + "!"

Also add this import near the top if you choose to annotate _astream’s return type later:

from collections.abc import AsyncIterator

413-419: Silence ARG001 by marking unused config param.

Rename the unused argument to _config.

-async def register_failing_function_group(config: FailingFunctionGroupConfig, _builder: Builder):
+async def register_failing_function_group(_config: FailingFunctionGroupConfig, _builder: Builder):

291-293: TRY003 nit: long exception messages.

Ruff flags “Avoid specifying long messages outside the exception class.” Fine to ignore in tests; if you prefer clean runs, either shorten messages, add a tiny custom exception, or annotate lines with # noqa: TRY003.

Also applies to: 318-320, 345-347, 362-364, 389-391


800-808: Annotate return type for the test retriever.

Make the stub explicit for readability and tooling.

-        async def search(self, query: str, **kwargs):
+        async def search(self, query: str, **kwargs) -> RetrieverOutput:
             return RetrieverOutput(results=[Document(page_content="page content", metadata={})])

1374-1390: Fixture naming: add name= and use fixture_ prefix per guidelines.

Keeps param names stable while satisfying style rules.

-@pytest.fixture
-def caplog_fixture(caplog):
+@pytest.fixture(name="caplog_fixture")
+def fixture_caplog_fixture(caplog):
@@
-@pytest.fixture
-def mock_component_data():
+@pytest.fixture(name="mock_component_data")
+def fixture_mock_component_data():

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

🧹 Nitpick comments (6)
tests/nat/builder/test_builder.py (6)

139-142: Fix docstring: “excludes” not “includes”.

The class documents “includes all functions” but this config uses exclude to hide all.

 class AllExcludesFunctionGroupConfig(FunctionGroupBaseConfig, name="all_excludes_function_group"):
-    """Test configuration that includes all functions."""
+    """Test configuration that excludes all functions."""
     exclude: list[str] = Field(default_factory=lambda: ["add", "multiply", "subtract"])
     raise_error: bool = False

412-419: Silence unused-arg lint on config param.

Rename to _config to satisfy Ruff ARG001 without changing behavior.

-    async def register_failing_function_group(config: FailingFunctionGroupConfig, _builder: Builder):
+    async def register_failing_function_group(_config: FailingFunctionGroupConfig, _builder: Builder):
         """Register a function group that always fails during initialization."""
 
         # This function group always raises an exception during initialization
         raise ValueError("Function group initialization failed")
         yield  # This line will never be reached, but needed for the AsyncGenerator type

1054-1065: Use the config type as the function-name prefix, not the builder name.

Function names are prefixed by the group’s instance_name (defaults to config.type, here “default_function_group”), so checking for "empty_group." is misleading even though the test currently passes.

-        included_functions = [k for k in builder._functions.keys() if k.startswith("empty_group.")]
+        included_functions = [k for k in builder._functions.keys() if k.startswith("default_function_group.")]

921-934: Fix variable shadowing and tautological assertion.

Avoid reusing workflow_config and assert against the originally set workflow config.

-        workflow = builder.build()
-
-        workflow_config = workflow.config
-
-        assert workflow_config.general == general_config
-        assert workflow_config.functions == {"function1": function_config}
-        assert workflow_config.workflow == workflow_config.workflow
-        assert workflow_config.llms == {"llm1": llm_config}
-        assert workflow_config.embedders == {"embedder1": embedder_config}
-        assert workflow_config.memory == {"memory1": memory_config}
-        assert workflow_config.retrievers == {"retriever1": retriever_config}
-        assert workflow_config.object_stores == {"object_store1": object_store_config}
-        assert workflow_config.ttc_strategies == {"ttc_strategy": ttc_config}
+        workflow = builder.build()
+
+        built_config = workflow.config
+
+        assert built_config.general == general_config
+        assert built_config.functions == {"function1": function_config}
+        assert built_config.workflow == workflow_config
+        assert built_config.llms == {"llm1": llm_config}
+        assert built_config.embedders == {"embedder1": embedder_config}
+        assert built_config.memory == {"memory1": memory_config}
+        assert built_config.retrievers == {"retriever1": retriever_config}
+        assert built_config.object_stores == {"object_store1": object_store_config}
+        assert built_config.ttc_strategies == {"ttc_strategy": ttc_config}

1367-1368: Prefer isinstance over issubclass(type(...), ...).

Clearer and idiomatic.

-        assert issubclass(type(exporter1_instance), BaseExporter)
+        assert isinstance(exporter1_instance, BaseExporter)

150-152: Fixture naming consistency (optional).

For consistency with repo guidelines, consider using a fixture_ prefix and setting name=... on the decorator, even for autouse fixtures. Behavior unchanged.

-@pytest.fixture(scope="module", autouse=True)
-async def _register():
+@pytest.fixture(scope="module", autouse=True, name="fixture_register")
+async def fixture_register():
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28691cf and 1c98afd.

📒 Files selected for processing (1)
  • tests/nat/builder/test_builder.py (16 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Unit tests must live under tests/ and use configured markers (e2e, integration, etc.)

Files:

  • tests/nat/builder/test_builder.py

⚙️ CodeRabbit configuration file

tests/**/*.py: - Ensure that tests are comprehensive, cover edge cases, and validate the functionality of the code. - Test functions should be named using the test_ prefix, using snake_case. - Any frequently repeated code should be extracted into pytest fixtures. - Pytest fixtures should define the name argument when applying the pytest.fixture decorator. The fixture
function being decorated should be named using the fixture_ prefix, using snake_case. Example:
@pytest.fixture(name="my_fixture")
def fixture_my_fixture():
pass

Files:

  • tests/nat/builder/test_builder.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • tests/nat/builder/test_builder.py
**/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/tests/**/*.py: Test functions must use the test_ prefix and snake_case
Extract repeated test code into pytest fixtures; fixtures should set name=... in @pytest.fixture and functions named with fixture_ prefix
Mark expensive tests with @pytest.mark.slow or @pytest.mark.integration
Use pytest with pytest-asyncio for async code; mock external services with pytest_httpserver or unittest.mock

Files:

  • tests/nat/builder/test_builder.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • tests/nat/builder/test_builder.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • tests/nat/builder/test_builder.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • tests/nat/builder/test_builder.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • tests/nat/builder/test_builder.py
🧠 Learnings (6)
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.

Applied to files:

  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.

Applied to files:

  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T14:30:12.178Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.

Applied to files:

  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T20:03:37.650Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.

Applied to files:

  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are separate class hierarchies that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other.

Applied to files:

  • tests/nat/builder/test_builder.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are sibling classes that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other and the order doesn't matter for classification purposes.

Applied to files:

  • tests/nat/builder/test_builder.py
🧬 Code graph analysis (1)
tests/nat/builder/test_builder.py (4)
src/nat/builder/function.py (8)
  • Function (45-300)
  • FunctionGroup (350-512)
  • add_function (371-411)
  • get_accessible_functions (437-459)
  • get_all_functions (503-512)
  • get_included_functions (481-501)
  • get_excluded_functions (461-479)
  • get_config (413-422)
src/nat/cli/register_workflow.py (2)
  • register_function_group (180-211)
  • register_function (148-177)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (11)
  • add_function (412-423)
  • add_function (1111-1112)
  • add_function_group (426-448)
  • add_function_group (1115-1116)
  • get_function_group (460-466)
  • get_function_group (1128-1134)
  • get_function_group_config (478-484)
  • get_function_group_config (1141-1142)
  • get_function (451-457)
  • get_function (1119-1125)
  • dependencies (1107-1108)
🪛 Ruff (0.12.2)
tests/nat/builder/test_builder.py

292-292: Avoid specifying long messages outside the exception class

(TRY003)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Avoid specifying long messages outside the exception class

(TRY003)


363-363: Avoid specifying long messages outside the exception class

(TRY003)


390-390: Avoid specifying long messages outside the exception class

(TRY003)


413-413: Unused function argument: config

(ARG001)


417-417: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (2)
tests/nat/builder/test_builder.py (2)

20-23: Pydantic import corrections — LGTM.

Switching to Pydantic’s BaseModel and Field aligns with the codebase and fixes prior import issues.


114-148: FunctionGroup config defaults — LGTM.

Using Field(default_factory=...) for include/exclude matches FunctionGroupBaseConfig semantics (empty lists by default) and avoids mutable default pitfalls.

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

Caution

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

⚠️ Outside diff range comments (1)
src/nat/agent/tool_calling_agent/register.py (1)

53-55: Add return type annotation (public API).

Annotate the async generator per project guidelines.

-from nat.builder.builder import Builder
+from nat.builder.builder import Builder
+from collections.abc import AsyncIterator
@@
-async def tool_calling_agent_workflow(config: ToolCallAgentWorkflowConfig, builder: Builder):
+async def tool_calling_agent_workflow(
+    config: ToolCallAgentWorkflowConfig, builder: Builder
+) -> AsyncIterator[FunctionInfo]:
♻️ Duplicate comments (8)
src/nat/agent/tool_calling_agent/register.py (1)

39-40: Clarify field description to include groups.

Update to reflect that entries can be functions or function groups.

-    tool_names: list[FunctionRef | FunctionGroupRef] = Field(
-        default_factory=list, description="The list of tools to provide to the tool calling agent.")
+    tool_names: list[FunctionRef | FunctionGroupRef] = Field(
+        default_factory=list,
+        description="The list of functions and/or function groups to provide to the tool calling agent.",
+    )
src/nat/data_models/function.py (1)

44-50: Preserve caller order for include/exclude (don’t sort).

Sorting alters user-specified order and can affect deterministic tool exposure.

     @field_validator("include", "exclude")
     @classmethod
     def _validate_fields_include_exclude(cls, value: list[str]) -> list[str]:
         if len(set(value)) != len(value):
             raise ValueError("Function names must be unique")
-        return sorted(value)
+        return value
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (3)

147-150: Align add_function_group signature with Builder API.

-    async def add_function_group(self, name: str, config: FunctionGroupBaseConfig) -> FunctionGroup:
+    async def add_function_group(self, name: str | FunctionGroupRef, config: FunctionGroupBaseConfig) -> FunctionGroup:
         """Mock implementation - not used in tool testing."""
         raise NotImplementedError("Mock implementation does not support add_function_group")

159-162: Match get_function_group_config signature with Builder.

-    def get_function_group_config(self, name: str) -> FunctionGroupBaseConfig:
+    def get_function_group_config(self, name: str | FunctionGroupRef) -> FunctionGroupBaseConfig:
         """Mock implementation."""
         return FunctionGroupBaseConfig()

151-158: Widen get_function_group signature; index mocks by str(name).

-    def get_function_group(self, name: str) -> FunctionGroup:
+    def get_function_group(self, name: str | FunctionGroupRef) -> FunctionGroup:
         """Return a mock function group if one is configured."""
-        if name in self._mocks:
+        key = str(name)
+        if key in self._mocks:
-            mock_fn_group = MagicMock(spec=FunctionGroup)
-            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[name])
+            mock_fn_group = MagicMock(spec=FunctionGroup)
+            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[key])
             return mock_fn_group
         raise ValueError(f"Function group '{name}' not mocked. Use mock_function_group() to add it.")
examples/object_store/user_report/configs/config_mem.yml (1)

70-71: Add tests to verify tool_names expansion order (get → put → update → delete).

Ensure [user_report] expands to namespaced functions in expose/registration order.

Run:

#!/bin/bash
rg -nP -C2 'tool_names:\s*\[\s*user_report\s*\]' examples tests
rg -nP -C2 '\bexposes:\s*\[\s*get.*put.*update.*delete\s*\]' examples || true
# If a test exists, it should resolve to:
# ["user_report.get","user_report.put","user_report.update","user_report.delete"] in that order.
src/nat/tool/github_tools.py (1)

424-429: Build correct raw URLs (remove refs/heads, handle 1-based lines).

raw.githubusercontent.com should be owner/repo/branch/path.

-            # The following URL is the raw URL of the file. refs/heads/ always points to the top commit of the branch
-            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/refs/heads/{file_metadata.file_path}"
+            # Build raw URL: owner/repo/branch/path
+            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/{file_metadata.file_path}"
             try:
                 response = await client.get(raw_url)
                 response.raise_for_status()
src/nat/builder/workflow_builder.py (1)

1166-1176: Dependency tracking misses FunctionGroupRef/FunctionRef due to no normalization.

Membership tests against dict[str, …] fail for ComponentRef objects; also dependency keys should be strings.

-        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
-        for tool_name in tool_names:
-            if tool_name in self._workflow_builder._function_groups:
-                self._dependencies.add_function_group(tool_name)
-            else:
-                self._dependencies.add_function(tool_name)
+        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
+        for tool_name in tool_names:
+            key = str(tool_name)
+            if key in self._workflow_builder._function_groups:
+                self._dependencies.add_function_group(key)
+            else:
+                self._dependencies.add_function(key)
🧹 Nitpick comments (27)
src/nat/agent/tool_calling_agent/register.py (2)

45-45: Fix typo in description.

-    max_iterations: int = Field(default=15, description="Number of tool calls before stoping the tool calling agent.")
+    max_iterations: int = Field(default=15, description="Number of tool calls before stopping the tool calling agent.")

68-71: Tighten error message.

Message references llm_name while checking tools; make it tool-centric.

-    if not tools:
-        raise ValueError(f"No tools specified for Tool Calling Agent '{config.llm_name}'")
+    if not tools:
+        raise ValueError("No tools specified for Tool Calling Agent workflow.")
src/nat/data_models/function.py (1)

51-56: Annotate model validator return type.

-    @model_validator(mode="after")
-    def _validate_include_exclude(self):
+    @model_validator(mode="after")
+    def _validate_include_exclude(self) -> "FunctionGroupBaseConfig":
         if self.include and self.exclude:
             raise ValueError("include and exclude cannot be used together")
         return self
src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py (2)

100-104: Guard missing/absent group dependencies to avoid KeyError/AttributeError.

-    function_dependencies = builder.get_function_dependencies(config.augmented_fn)
-    function_used_tools = set(function_dependencies.functions)
-    for function_group in function_dependencies.function_groups:
-        function_used_tools.update(builder.get_function_group_dependencies(function_group).functions)
+    function_dependencies = builder.get_function_dependencies(config.augmented_fn)
+    function_used_tools = set(function_dependencies.functions)
+    for function_group in function_dependencies.function_groups:
+        try:
+            group_deps = builder.get_function_group_dependencies(function_group)
+        except KeyError:
+            group_deps = None
+        if group_deps and getattr(group_deps, "functions", None):
+            function_used_tools.update(group_deps.functions)

107-110: Make prompt/tool listing deterministic.

Stable ordering improves reproducibility and testing.

-    for tool in function_used_tools:
+    for tool in sorted(function_used_tools):
         tool_impl = builder.get_function(tool)
         tool_list += f"- {tool}: {tool_impl.description if hasattr(tool_impl, 'description') else ''}\n"
src/nat/data_models/function_dependencies.py (2)

33-39: Serialize sets in sorted order for deterministic JSON.

     @field_serializer("functions", when_used="json")
     def serialize_functions(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)
@@
     @field_serializer("function_groups", when_used="json")
     def serialize_function_groups(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)
@@
     @field_serializer("llms", when_used="json")
     def serialize_llms(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)
@@
     @field_serializer("embedders", when_used="json")
     def serialize_embedders(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)
@@
     @field_serializer("memory_clients", when_used="json")
     def serialize_memory_clients(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)
@@
     @field_serializer("object_stores", when_used="json")
     def serialize_object_stores(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)
@@
     @field_serializer("retrievers", when_used="json")
     def serialize_retrievers(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

Also applies to: 41-59


64-66: Remove unnecessary pylint disable.

No dynamic attribute; field exists.

-    def add_function_group(self, function_group: str):
-        self.function_groups.add(function_group)  # pylint: disable=no-member
+    def add_function_group(self, function_group: str):
+        self.function_groups.add(function_group)
examples/object_store/user_report/README.md (2)

20-20: Fix article typo.

-And example tool in the NeMo Agent toolkit that makes use of an Object Store to retrieve data.
+An example tool in the NeMo Agent toolkit that makes use of an Object Store to retrieve data.

124-128: Fix broken docker-compose links (missing “examples/” path segment).

-You can use the [docker-compose.minio.yml](../../deploy/docker-compose.minio.yml) file to start a MinIO server in a local docker container.
+You can use the [docker-compose.minio.yml](../../examples/deploy/docker-compose.minio.yml) file to start a MinIO server in a local docker container.
@@
-If you want to use a MySQL server, you can use the [docker-compose.mysql.yml](../../deploy/docker-compose.mysql.yml) file to start a MySQL server in a local docker container.
+If you want to use a MySQL server, you can use the [docker-compose.mysql.yml](../../examples/deploy/docker-compose.mysql.yml) file to start a MySQL server in a local docker container.
@@
-If you want to use a Redis server, you can use the [docker-compose.redis.yml](../../deploy/docker-compose.redis.yml) file to start a Redis server in a local docker container.
+If you want to use a Redis server, you can use the [docker-compose.redis.yml](../../examples/deploy/docker-compose.redis.yml) file to start a Redis server in a local docker container.

Also applies to: 135-137, 154-157

tests/nat/agent/test_reasoning_agent.py (3)

141-146: Annotate mutable class attributes as ClassVar (ruff RUF012).

     class FakeDeps:
-        functions = {"SomeTool"}
-        function_groups = set()
+        functions: ClassVar[set[str]] = {"SomeTool"}
+        function_groups: ClassVar[set[str]] = set()

Add import (outside this hunk):

from typing import ClassVar

305-310: Repeat ClassVar annotations for this FakeDeps.

     class FakeDeps:
-        functions = {"ToolA", "ToolB"}
-        function_groups = set()
+        functions: ClassVar[set[str]] = {"ToolA", "ToolB"}
+        function_groups: ClassVar[set[str]] = set()

380-385: Repeat ClassVar annotations for empty-set FakeDeps.

     class FakeDeps:
-        functions = set()
-        function_groups = set()
+        functions: ClassVar[set[str]] = set()
+        function_groups: ClassVar[set[str]] = set()
examples/object_store/user_report/configs/config_mem.yml (1)

68-75: Stub the LLM in example configs for CI.

Repo rules require nat_test_llm in YAML. Either switch this example to a stub or add a test variant (e.g., config_mem.test.yml) that uses nat_test_llm with response_seq and delay_ms, and point workflow.llm_name to it during tests.

Example test config snippet:

llms:
  test_llm:
    _type: nat_test_llm
    response_seq: ["ok"]
    delay_ms: 0

workflow:
  _type: react_agent
  tool_names: [user_report]
  llm_name: test_llm
  verbose: true
  handle_parsing_errors: true
  max_retries: 2
examples/object_store/user_report/src/nat_user_report/user_report_tools.py (2)

18-25: Annotate async generator and import AsyncIterator.

Conform to public API typing rules.

+from typing import AsyncIterator
@@
-@register_function_group(config_type=UserReportConfig)
-async def user_report_group(config: UserReportConfig, builder: Builder):
+@register_function_group(config_type=UserReportConfig)
+async def user_report_group(config: UserReportConfig, builder: Builder) -> AsyncIterator[FunctionGroup]:

Also applies to: 46-49


68-80: Align put() behavior with spec (“return a conflict error”).

Currently returns a success-like string. Consider raising or returning a structured error to match the description.

-        except KeyAlreadyExistsError:
-            return f"User report for {user_id} with date {date} already exists"
+        except KeyAlreadyExistsError:
+            raise

If raising is undesirable, return a structured error object/string prefixed with “ConflictError”.

src/nat/builder/function.py (2)

16-18: Tighten validation, add return type, and precompile regex (fix Ruff TRY003).

Shorten error messages, strip once, and annotate return.

 import logging
-import re
+import re
@@
-    def add_function(self,
+    NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
+
+    def add_function(self,
                      name: str,
                      fn: Callable,
                      *,
                      input_schema: type[BaseModel] | None = None,
                      description: str | None = None,
-                     converters: list[Callable] | None = None):
+                     converters: list[Callable[[typing.Any], typing.Any]] | None = None) -> None:
@@
-        if not name.strip():
-            raise ValueError("Function name cannot be empty or blank")
-        if not re.match(r"^[a-zA-Z0-9_-]+$", name):
-            raise ValueError(f"Function name can only contain letters, numbers, underscores, and hyphens: {name}")
-        if name in self._functions:
-            raise ValueError(f"Function {name} already exists in function group {self._instance_name}")
+        name = name.strip()
+        if not name:
+            raise ValueError("Empty function name.")
+        if not NAME_RE.match(name):
+            raise ValueError(f"Invalid function name: {name!r}.")
+        if name in self._functions:
+            raise ValueError(f"Duplicate function: {name!r}.")

Also applies to: 371-407


431-436: Compact error strings to satisfy Ruff TRY003.

Keep semantics; reduce message length.

-        missing = set(self._config.exclude) - set(self._functions.keys())
-        if missing:
-            raise ValueError(f"Unknown excluded functions: {sorted(missing)}")
+        missing = set(self._config.exclude) - set(self._functions.keys())
+        if missing:
+            raise ValueError(f"Unknown excluded: {sorted(missing)}")
@@
-        missing = set(self._config.include) - set(self._functions.keys())
-        if missing:
-            raise ValueError(f"Unknown included functions: {sorted(missing)}")
+        missing = set(self._config.include) - set(self._functions.keys())
+        if missing:
+            raise ValueError(f"Unknown included: {sorted(missing)}")

Also applies to: 476-501

src/nat/tool/github_tools.py (4)

16-18: Annotate async generators and import AsyncIterator.

Required by repo typing rules.

-from typing import Literal
+from typing import Literal, AsyncIterator
@@
-@register_function_group(config_type=GithubGroupConfig)
-async def github_tool(config: GithubGroupConfig, _builder: Builder):
+@register_function_group(config_type=GithubGroupConfig)
+async def github_tool(config: GithubGroupConfig, _builder: Builder) -> AsyncIterator[FunctionGroup]:
@@
-@register_function(config_type=GithubFilesGroupConfig)
-async def github_files_tool(config: GithubFilesGroupConfig, _builder: Builder):
+@register_function(config_type=GithubFilesGroupConfig)
+async def github_files_tool(config: GithubFilesGroupConfig, _builder: Builder) -> AsyncIterator[FunctionInfo]:

Also applies to: 151-160, 377-381


172-174: Tighten auth error message and add GitHub API version header.

Short message fixes TRY003; adding X-GitHub-Api-Version is recommended.

-    if not token:
-        raise ValueError("No GitHub token found in environment variables. Please set one of the following"
-                         "environment variables: GITHUB_TOKEN, GITHUB_PAT, GH_TOKEN")
+    if not token:
+        raise ValueError("Missing GitHub auth token (set GITHUB_TOKEN/GITHUB_PAT/GH_TOKEN).")
@@
     headers = {
         "Authorization": f"Bearer {token}",
         "Accept": "application/vnd.github+json",
-        "User-Agent": "NeMo-Agent-Toolkit",
+        "X-GitHub-Api-Version": "2022-11-28",
+        "User-Agent": "NeMo-Agent-Toolkit",
     }

Also applies to: 176-181


195-203: Encode labels as comma-separated string for list issues.

GitHub expects labels CSV, not repeated query params.

-            for issue in issues_list.filter_parameters:
-                params = issue.model_dump(exclude_unset=True, exclude_none=True)
+            for issue in issues_list.filter_parameters:
+                params = issue.model_dump(exclude_unset=True, exclude_none=True)
+                if isinstance(params.get("labels"), list):
+                    params["labels"] = ",".join(params["labels"])
                 response = await client.get(url, params=params)

359-363: Fix get_pull description (lists PRs, does not fetch files).

Clarify user-facing text.

-        group.add_function("get_pull",
-                           get_pull,
-                           description="Fetches the files for a particular GitHub pull request"
-                           f"in the repo named {config.repo_name}",
-                           input_schema=GithubGetPullsModelList)
+        group.add_function("get_pull",
+                           get_pull,
+                           description=f"Lists pull requests in {config.repo_name} with optional filters",
+                           input_schema=GithubGetPullsModelList)
src/nat/cli/type_registry.py (1)

186-194: Register/get FunctionGroup API: good structure; shorten error messages (TRY003).

Keep behavior; compact strings to satisfy Ruff.

-        if (registration.config_type in self._registered_function_groups):
-            raise ValueError(
-                f"A function group with the same config type `{registration.config_type}` has already been "
-                "registered.")
+        if registration.config_type in self._registered_function_groups:
+            raise ValueError("Function group already registered for this config type.")
@@
-        except KeyError as err:
-            raise KeyError(f"Could not find a registered function group for config `{config_type}`. "
-                           f"Registered configs: {set(self._registered_function_groups.keys())}") from err
+        except KeyError as err:
+            raise KeyError("No function group registered for given config type.") from err

Also applies to: 331-333, 499-542

src/nat/builder/workflow_builder.py (5)

246-246: Redundant variable initialization (shadowing).

function_instances is initialized twice; drop the first to avoid confusion.

-        # Dictionary of function instances
-        function_instances = dict()
...
-        function_instances = dict()
+        # Dictionary of function instances (populated below)
+        function_instances = dict()

Also applies to: 257-257


405-413: Duplicate dependency entries for function groups.

You seed dependencies under config.type, then store under name; this leaves a stray key. Seed under the final key.

-        self.current_function_group_building = config.type
-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        self.current_function_group_building = config.type
+        # Seed under the instance key used externally
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies

570-586: Add return annotation and preserve traceback details in log.

Public API requires return types; also log with exc_info and keep bare raise.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
@@
-        except Exception as e:
-            logger.error("Error fetching tool `%s`: %s", fn_name, e)
-            raise
+        except Exception:
+            logger.error("Error fetching tool `%s`", fn_name, exc_info=True)
+            raise

604-620: Missing return annotations on framework-dependent getters.

These public APIs should declare return types. Use typing.Any for wrapper-dependent returns.

-    async def get_llm(self, llm_name: str | LLMRef, wrapper_type: LLMFrameworkEnum | str):
+    async def get_llm(self, llm_name: str | LLMRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
@@
-    async def get_embedder(self, embedder_name: str | EmbedderRef, wrapper_type: LLMFrameworkEnum | str):
+    async def get_embedder(self, embedder_name: str | EmbedderRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
@@
-    async def get_retriever(self,
+    async def get_retriever(self,
                             retriever_name: str | RetrieverRef,
                             wrapper_type: LLMFrameworkEnum | str | None = None):
+        # Return type depends on wrapper; annotate as Any in the interface if desired.

Would you prefer annotating these to concrete protocol types instead of Any (if wrappers implement a shared interface)?

Also applies to: 720-739, 829-851


1178-1184: Normalize dependency key in ChildBuilder.get_tool.

Ensure dependencies record string names even when a FunctionRef is passed.

-        fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
-
-        self._dependencies.add_function(fn_name)
+        fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
+        self._dependencies.add_function(str(fn_name))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c98afd and e8c227c.

📒 Files selected for processing (42)
  • ci/scripts/path_checks.py (1 hunks)
  • docs/source/extend/function-groups.md (1 hunks)
  • docs/source/index.md (2 hunks)
  • docs/source/workflows/function-groups.md (1 hunks)
  • examples/object_store/user_report/README.md (8 hunks)
  • examples/object_store/user_report/configs/config_mem.yml (1 hunks)
  • examples/object_store/user_report/configs/config_mysql.yml (1 hunks)
  • examples/object_store/user_report/configs/config_redis.yml (1 hunks)
  • examples/object_store/user_report/configs/config_s3.yml (1 hunks)
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py (3 hunks)
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5 hunks)
  • src/nat/agent/react_agent/register.py (2 hunks)
  • src/nat/agent/reasoning_agent/reasoning_agent.py (1 hunks)
  • src/nat/agent/rewoo_agent/register.py (2 hunks)
  • src/nat/agent/tool_calling_agent/register.py (2 hunks)
  • src/nat/builder/builder.py (5 hunks)
  • src/nat/builder/component_utils.py (4 hunks)
  • src/nat/builder/function.py (3 hunks)
  • src/nat/builder/workflow.py (5 hunks)
  • src/nat/builder/workflow_builder.py (17 hunks)
  • src/nat/cli/register_workflow.py (4 hunks)
  • src/nat/cli/type_registry.py (10 hunks)
  • src/nat/data_models/component.py (2 hunks)
  • src/nat/data_models/component_ref.py (1 hunks)
  • src/nat/data_models/config.py (10 hunks)
  • src/nat/data_models/function.py (2 hunks)
  • src/nat/data_models/function_dependencies.py (3 hunks)
  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py (1 hunks)
  • src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py (1 hunks)
  • src/nat/profiler/utils.py (2 hunks)
  • src/nat/tool/github_tools.py (1 hunks)
  • src/nat/tool/github_tools/create_github_commit.py (0 hunks)
  • src/nat/tool/github_tools/create_github_issue.py (0 hunks)
  • src/nat/tool/github_tools/create_github_pr.py (0 hunks)
  • src/nat/tool/github_tools/get_github_file.py (0 hunks)
  • src/nat/tool/github_tools/get_github_issue.py (0 hunks)
  • src/nat/tool/github_tools/get_github_pr.py (0 hunks)
  • src/nat/tool/github_tools/update_github_issue.py (0 hunks)
  • src/nat/tool/register.py (1 hunks)
  • tests/nat/agent/test_reasoning_agent.py (3 hunks)
  • tests/nat/builder/test_builder.py (16 hunks)
  • tests/nat/front_ends/mcp/test_mcp_custom_routes.py (2 hunks)
💤 Files with no reviewable changes (7)
  • src/nat/tool/github_tools/create_github_pr.py
  • src/nat/tool/github_tools/get_github_file.py
  • src/nat/tool/github_tools/create_github_commit.py
  • src/nat/tool/github_tools/update_github_issue.py
  • src/nat/tool/github_tools/create_github_issue.py
  • src/nat/tool/github_tools/get_github_issue.py
  • src/nat/tool/github_tools/get_github_pr.py
✅ Files skipped from review due to trivial changes (1)
  • docs/source/workflows/function-groups.md
🚧 Files skipped from review as they are similar to previous changes (18)
  • tests/nat/front_ends/mcp/test_mcp_custom_routes.py
  • docs/source/index.md
  • src/nat/agent/reasoning_agent/reasoning_agent.py
  • ci/scripts/path_checks.py
  • examples/object_store/user_report/configs/config_s3.yml
  • src/nat/data_models/component_ref.py
  • src/nat/agent/react_agent/register.py
  • src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py
  • src/nat/profiler/utils.py
  • src/nat/cli/register_workflow.py
  • src/nat/tool/register.py
  • docs/source/extend/function-groups.md
  • src/nat/builder/builder.py
  • examples/object_store/user_report/configs/config_mysql.yml
  • examples/object_store/user_report/configs/config_redis.yml
  • src/nat/builder/workflow.py
  • src/nat/agent/rewoo_agent/register.py
  • src/nat/builder/component_utils.py
🧰 Additional context used
📓 Path-based instructions (15)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • src/nat/agent/tool_calling_agent/register.py
  • src/nat/builder/function.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • src/nat/agent/tool_calling_agent/register.py
  • src/nat/builder/function.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • src/nat/agent/tool_calling_agent/register.py
  • src/nat/builder/function.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • src/nat/agent/tool_calling_agent/register.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • examples/object_store/user_report/README.md
  • src/nat/agent/tool_calling_agent/register.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • examples/object_store/user_report/configs/config_mem.yml
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • examples/object_store/user_report/README.md
  • src/nat/agent/tool_calling_agent/register.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
  • tests/nat/agent/test_reasoning_agent.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • src/nat/agent/tool_calling_agent/register.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • examples/object_store/user_report/configs/config_mem.yml
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
  • tests/nat/agent/test_reasoning_agent.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py
  • src/nat/cli/type_registry.py
  • src/nat/data_models/component.py
  • src/nat/data_models/function_dependencies.py
  • examples/object_store/user_report/README.md
  • src/nat/agent/tool_calling_agent/register.py
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • examples/object_store/user_report/configs/config_mem.yml
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
  • src/nat/data_models/function.py
  • src/nat/tool/github_tools.py
  • tests/nat/agent/test_reasoning_agent.py
**/README.{md,ipynb}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Each documentation README must follow the NeMo Agent toolkit naming rules and must not use deprecated names

Files:

  • examples/object_store/user_report/README.md
examples/**/*

⚙️ CodeRabbit configuration file

examples/**/*: - This directory contains example code and usage scenarios for the toolkit, at a minimum an example should
contain a README.md or file README.ipynb.

  • If an example contains Python code, it should be placed in a subdirectory named src/ and should
    contain a pyproject.toml file. Optionally, it might also contain scripts in a scripts/ directory.
  • If an example contains YAML files, they should be placed in a subdirectory named configs/. - If an example contains sample data files, they should be placed in a subdirectory named data/, and should
    be checked into git-lfs.

Files:

  • examples/object_store/user_report/README.md
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
  • examples/object_store/user_report/configs/config_mem.yml
tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Unit tests must live under tests/ and use configured markers (e2e, integration, etc.)

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py

⚙️ CodeRabbit configuration file

tests/**/*.py: - Ensure that tests are comprehensive, cover edge cases, and validate the functionality of the code. - Test functions should be named using the test_ prefix, using snake_case. - Any frequently repeated code should be extracted into pytest fixtures. - Pytest fixtures should define the name argument when applying the pytest.fixture decorator. The fixture
function being decorated should be named using the fixture_ prefix, using snake_case. Example:
@pytest.fixture(name="my_fixture")
def fixture_my_fixture():
pass

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
**/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/tests/**/*.py: Test functions must use the test_ prefix and snake_case
Extract repeated test code into pytest fixtures; fixtures should set name=... in @pytest.fixture and functions named with fixture_ prefix
Mark expensive tests with @pytest.mark.slow or @pytest.mark.integration
Use pytest with pytest-asyncio for async code; mock external services with pytest_httpserver or unittest.mock

Files:

  • tests/nat/builder/test_builder.py
  • tests/nat/agent/test_reasoning_agent.py
**/configs/**/*.y?(a)ml

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Configuration files consumed by code must live in a neighbouring configs/ directory

Files:

  • examples/object_store/user_report/configs/config_mem.yml
**/*.{yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

In workflow/config YAML, set llms.._type: nat_test_llm to stub responses.

Files:

  • examples/object_store/user_report/configs/config_mem.yml
packages/*/src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

packages/*/src/**/*.py: All importable Python code in packages must live under packages//src/
All public APIs in packaged code require Python 3.11+ type hints; prefer typing/collections.abc; use typing.Annotated when useful
Provide Google-style docstrings for public APIs in packages; first line concise with a period; use backticks for code entities

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
packages/**/*

⚙️ CodeRabbit configuration file

packages/**/*: - This directory contains optional plugin packages for the toolkit, each should contain a pyproject.toml file. - The pyproject.toml file should declare a dependency on nvidia-nat or another package with a name starting
with nvidia-nat-. This dependency should be declared using ~=<version>, and the version should be a two
digit version (ex: ~=1.0).

  • Not all packages contain Python code, if they do they should also contain their own set of tests, in a
    tests/ directory at the same level as the pyproject.toml file.

Files:

  • packages/nvidia_nat_test/src/nat/test/tool_test_runner.py
🧠 Learnings (8)
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are sibling classes that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other and the order doesn't matter for classification purposes.

Applied to files:

  • src/nat/cli/type_registry.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are separate class hierarchies that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other.

Applied to files:

  • src/nat/cli/type_registry.py
  • tests/nat/builder/test_builder.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T20:03:37.650Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.

Applied to files:

  • examples/object_store/user_report/README.md
  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • examples/object_store/user_report/src/nat_user_report/user_report_tools.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.

Applied to files:

  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None, so there are no None-handling concerns for this field.

Applied to files:

  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:30:12.178Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.

Applied to files:

  • tests/nat/builder/test_builder.py
  • src/nat/builder/function.py
  • src/nat/data_models/config.py
  • src/nat/builder/workflow_builder.py
  • src/nat/data_models/function.py
📚 Learning: 2025-08-28T14:29:34.897Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/workflow_builder.py:373-410
Timestamp: 2025-08-28T14:29:34.897Z
Learning: ComponentRef objects (like FunctionGroupRef) automatically convert to strings when used as dictionary keys in Python, so explicit str() conversions are not required when using them as dict keys.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T23:22:41.742Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-08-28T23:22:41.742Z
Learning: Applies to **/*.py : When re-raising exceptions, use bare `raise` and log with logger.error(), not logger.exception()

Applied to files:

  • src/nat/builder/workflow_builder.py
🧬 Code graph analysis (13)
src/nat/experimental/test_time_compute/functions/plan_select_execute_function.py (3)
src/nat/builder/workflow_builder.py (4)
  • get_function_dependencies (523-526)
  • get_function_dependencies (1305-1306)
  • get_function_group_dependencies (529-532)
  • get_function_group_dependencies (1309-1310)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)
  • get_function_dependencies (284-286)
  • get_function_group_dependencies (288-290)
src/nat/builder/builder.py (2)
  • get_function_dependencies (276-277)
  • get_function_group_dependencies (280-281)
src/nat/cli/type_registry.py (8)
src/nat/builder/function.py (1)
  • FunctionGroup (350-512)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/builder.py (2)
  • Builder (64-281)
  • get_function_group (79-80)
src/nat/cli/register_workflow.py (1)
  • register_function_group (180-211)
src/nat/builder/workflow_builder.py (2)
  • get_function_group (469-475)
  • get_function_group (1137-1143)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)
  • get_function_group (151-157)
src/nat/data_models/component.py (1)
  • ComponentEnum (22-43)
src/nat/data_models/common.py (1)
  • static_type (154-155)
src/nat/data_models/function_dependencies.py (3)
src/nat/builder/workflow_builder.py (2)
  • add_function_group (435-457)
  • add_function_group (1124-1125)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)
  • add_function_group (147-149)
src/nat/builder/builder.py (1)
  • add_function_group (71-72)
src/nat/agent/tool_calling_agent/register.py (1)
src/nat/data_models/component_ref.py (2)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
tests/nat/builder/test_builder.py (6)
src/nat/builder/builder.py (6)
  • Builder (64-281)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function_group (79-80)
  • get_function_group_config (94-95)
  • get_function (75-76)
src/nat/builder/function.py (8)
  • Function (45-300)
  • FunctionGroup (350-512)
  • add_function (371-411)
  • get_accessible_functions (437-459)
  • get_all_functions (503-512)
  • get_included_functions (481-501)
  • get_excluded_functions (461-479)
  • get_config (413-422)
src/nat/cli/register_workflow.py (2)
  • register_function_group (180-211)
  • register_function (148-177)
src/nat/cli/type_registry.py (4)
  • register_function_group (499-515)
  • register_function (477-485)
  • get_function_group (517-533)
  • get_function (487-493)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (12)
  • add_function (421-432)
  • add_function (1120-1121)
  • WorkflowBuilder (141-1104)
  • add_function_group (435-457)
  • add_function_group (1124-1125)
  • get_function_group (469-475)
  • get_function_group (1137-1143)
  • get_function_group_config (487-493)
  • get_function_group_config (1150-1151)
  • get_function (460-466)
  • get_function (1128-1134)
  • dependencies (1116-1117)
src/nat/builder/function.py (2)
src/nat/data_models/function.py (3)
  • EmptyFunctionConfig (58-59)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/context.py (1)
  • Context (93-277)
examples/object_store/user_report/src/nat_user_report/user_report_tools.py (6)
examples/object_store/user_report/tests/test_user_report_tool.py (2)
  • builder (33-49)
  • object_store (53-55)
src/nat/builder/builder.py (3)
  • Builder (64-281)
  • get_object_store_client (167-168)
  • add_function (67-68)
src/nat/builder/function.py (2)
  • FunctionGroup (350-512)
  • add_function (371-411)
src/nat/cli/register_workflow.py (1)
  • register_function_group (180-211)
src/nat/data_models/function.py (1)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (4)
  • get_object_store_client (796-800)
  • get_object_store_client (1250-1258)
  • add_function (421-432)
  • add_function (1120-1121)
src/nat/data_models/config.py (3)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/cli/type_registry.py (3)
  • get (1049-1050)
  • get_registered_function_groups (535-541)
  • compute_annotation (997-1041)
src/nat/data_models/common.py (2)
  • TypedBaseModel (93-168)
  • discriminator (162-168)
src/nat/builder/workflow_builder.py (8)
src/nat/builder/function.py (5)
  • FunctionGroup (350-512)
  • add_function (371-411)
  • Function (45-300)
  • get_included_functions (481-501)
  • get_accessible_functions (437-459)
src/nat/data_models/component_ref.py (12)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
  • component_group (69-76)
  • component_group (90-91)
  • component_group (101-102)
  • component_group (112-113)
  • component_group (123-124)
  • component_group (134-135)
  • component_group (145-146)
  • component_group (156-157)
  • component_group (167-168)
  • component_group (178-179)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/data_models/function_dependencies.py (5)
  • FunctionDependencies (21-80)
  • add_function (61-62)
  • add_function_group (64-65)
  • add_object_store (76-77)
  • add_retriever (79-80)
src/nat/data_models/config.py (1)
  • Config (239-426)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (11)
  • get_function_group (151-157)
  • add_function (131-133)
  • add_function_group (147-149)
  • get_function (135-141)
  • get_function_config (143-145)
  • get_function_group_config (159-161)
  • get_function_group_dependencies (288-290)
  • get_tools (179-181)
  • get_tool (183-185)
  • add_object_store (258-260)
  • add_retriever (241-243)
src/nat/builder/builder.py (11)
  • get_function_group (79-80)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function (75-76)
  • get_function_config (90-91)
  • get_function_group_config (94-95)
  • get_function_group_dependencies (280-281)
  • get_tools (110-113)
  • get_tool (116-117)
  • add_object_store (157-158)
  • add_retriever (217-218)
src/nat/profiler/decorators/framework_wrapper.py (1)
  • chain_wrapped_build_fn (108-128)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5)
src/nat/builder/builder.py (6)
  • Builder (64-281)
  • add_function_group (71-72)
  • get_function_group (79-80)
  • get_function_group_config (94-95)
  • get_tools (110-113)
  • get_function_group_dependencies (280-281)
src/nat/builder/function.py (2)
  • Function (45-300)
  • FunctionGroup (350-512)
src/nat/data_models/function.py (2)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
src/nat/builder/workflow_builder.py (10)
  • add_function_group (435-457)
  • add_function_group (1124-1125)
  • get_function_group (469-475)
  • get_function_group (1137-1143)
  • get_function_group_config (487-493)
  • get_function_group_config (1150-1151)
  • get_tools (535-568)
  • get_tools (1166-1175)
  • get_function_group_dependencies (529-532)
  • get_function_group_dependencies (1309-1310)
src/nat/data_models/function_dependencies.py (2)
  • add_function_group (64-65)
  • FunctionDependencies (21-80)
src/nat/data_models/function.py (1)
src/nat/data_models/common.py (2)
  • TypedBaseModel (93-168)
  • BaseModelRegistryTag (88-90)
src/nat/tool/github_tools.py (4)
src/nat/builder/builder.py (1)
  • Builder (64-281)
src/nat/builder/function.py (2)
  • FunctionGroup (350-512)
  • add_function (371-411)
src/nat/builder/function_info.py (2)
  • FunctionInfo (290-625)
  • from_fn (552-625)
src/nat/data_models/function.py (2)
  • FunctionBaseConfig (26-27)
  • FunctionGroupBaseConfig (30-55)
tests/nat/agent/test_reasoning_agent.py (2)
src/nat/builder/workflow_builder.py (4)
  • get_function_dependencies (523-526)
  • get_function_dependencies (1305-1306)
  • get_function_group_dependencies (529-532)
  • get_function_group_dependencies (1309-1310)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (2)
  • get_function_dependencies (284-286)
  • get_function_group_dependencies (288-290)
🪛 Ruff (0.12.2)
src/nat/cli/type_registry.py

509-511: Avoid specifying long messages outside the exception class

(TRY003)


532-533: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/builder/test_builder.py

292-292: Avoid specifying long messages outside the exception class

(TRY003)


319-319: Avoid specifying long messages outside the exception class

(TRY003)


346-346: Avoid specifying long messages outside the exception class

(TRY003)


363-363: Avoid specifying long messages outside the exception class

(TRY003)


390-390: Avoid specifying long messages outside the exception class

(TRY003)


413-413: Unused function argument: config

(ARG001)


417-417: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/builder/function.py

402-402: Avoid specifying long messages outside the exception class

(TRY003)


404-404: Avoid specifying long messages outside the exception class

(TRY003)


406-406: Avoid specifying long messages outside the exception class

(TRY003)


433-433: Avoid specifying long messages outside the exception class

(TRY003)


478-478: Avoid specifying long messages outside the exception class

(TRY003)


500-500: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/builder/workflow_builder.py

415-416: Prefer TypeError exception for invalid type

(TRY004)


415-416: Avoid specifying long messages outside the exception class

(TRY003)


426-426: Avoid specifying long messages outside the exception class

(TRY003)


440-440: Avoid specifying long messages outside the exception class

(TRY003)


451-451: Avoid specifying long messages outside the exception class

(TRY003)


464-464: Avoid specifying long messages outside the exception class

(TRY003)


473-473: Avoid specifying long messages outside the exception class

(TRY003)


482-482: Avoid specifying long messages outside the exception class

(TRY003)


491-491: Avoid specifying long messages outside the exception class

(TRY003)


545-545: Avoid specifying long messages outside the exception class

(TRY003)


550-550: Avoid specifying long messages outside the exception class

(TRY003)

packages/nvidia_nat_test/src/nat/test/tool_test_runner.py

157-157: Avoid specifying long messages outside the exception class

(TRY003)


159-159: Unused method argument: name

(ARG002)


179-179: Unused method argument: tool_names

(ARG002)


179-179: Unused method argument: wrapper_type

(ARG002)


288-288: Unused method argument: fn_name

(ARG002)

src/nat/data_models/function.py

48-48: Avoid specifying long messages outside the exception class

(TRY003)


54-54: Avoid specifying long messages outside the exception class

(TRY003)

src/nat/tool/github_tools.py

66-66: Avoid specifying long messages outside the exception class

(TRY003)


173-174: Avoid specifying long messages outside the exception class

(TRY003)


266-266: Avoid specifying long messages outside the exception class

(TRY003)


279-279: Avoid specifying long messages outside the exception class

(TRY003)


281-281: Avoid specifying long messages outside the exception class

(TRY003)

tests/nat/agent/test_reasoning_agent.py

141-141: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


142-142: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


305-305: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


306-306: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


380-380: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


381-381: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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

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

Caution

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

⚠️ Outside diff range comments (2)
src/nat/builder/workflow_builder.py (2)

570-586: Add return type to get_tool to meet public API typing contract.
Missing return annotation can fail pyright and violates repo guidelines.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:

1178-1185: Normalize dependency key and add return annotation in ChildBuilder.get_tool.
Ensures consistent dependency tracking and satisfies typing rules.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
         # If a function tries to get another function as a tool, we assume it uses it
         fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
-
-        self._dependencies.add_function(fn_name)
+        self._dependencies.add_function(str(fn_name))
 
         return fn
♻️ Duplicate comments (2)
src/nat/builder/workflow_builder.py (2)

406-413: Normalize function-group dependency key to instance name; avoid stray entry.
Seed and store under name only (not config.type) to prevent duplicate keys.

-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        # Empty set of dependencies for the current function group (by instance key)
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies

1165-1175: Fix FunctionGroupRef membership check; normalize keys before recording deps.
Currently misses groups and records as functions.

-        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
-        for tool_name in tool_names:
-            if tool_name in self._workflow_builder._function_groups:
-                self._dependencies.add_function_group(tool_name)
-            else:
-                self._dependencies.add_function(tool_name)
+        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
+        for tool_name in tool_names:
+            key = str(tool_name)
+            if key in self._workflow_builder._function_groups:
+                self._dependencies.add_function_group(key)
+            else:
+                self._dependencies.add_function(key)
         return tools
🧹 Nitpick comments (6)
src/nat/builder/workflow_builder.py (6)

241-262: Remove redundant initialization of function_instances.
Declaring function_instances twice is confusing; keep the latter.

-        # Dictionary of function instances
-        function_instances = dict()
@@
-        function_configs = dict()
-        function_instances = dict()
+        function_configs = dict()
+        function_instances = dict()

415-416: Prefer TypeError for invalid type from builder.
Aligns with Ruff TRY004 and idiomatic usage.

-        if not isinstance(build_result, FunctionGroup):
-            raise ValueError("Expected a FunctionGroup object to be returned from the function group builder. "
+        if not isinstance(build_result, FunctionGroup):
+            raise TypeError("Expected a FunctionGroup object to be returned from the function group builder. "
                              f"Got {type(build_result)}")

435-457: Avoid recomputing included functions; compute once.
Reduces duplicate calls and keeps a consistent snapshot.

-        # If the function group exposes functions, add them to the global function registry
-        # If the function group exposes functions, record and add them to the registry
-        for k in build_result.instance.get_included_functions():
+        # If the function group exposes functions, record and add them to the registry
+        included = build_result.instance.get_included_functions()
+        for k in included:
             if k in self._functions:
                 raise ValueError(f"Exposed function `{k}` from group `{name}` conflicts with an existing function")
-        self._functions.update({
-            k: ConfiguredFunction(config=v.config, instance=v)
-            for k, v in build_result.instance.get_included_functions().items()
-        })
+        self._functions.update({k: ConfiguredFunction(config=v.config, instance=v) for k, v in included.items()})

529-533: Double-check dependency keys are only instance names.
Given seeding under config.type earlier, ensure callers don’t rely on that stray key.


535-568: Tweak logging when re-raising to avoid duplicate stack traces.
Use logger.error without exc_info=True since you re-raise.

-                except Exception:
-                    logger.error("Error fetching tool `%s`", fn_name, exc_info=True)
-                    raise
+                except Exception:
+                    logger.error("Error fetching tool `%s`", fn_name)
+                    raise

1056-1056: Grammar nit: “an object store.”

-                # Instantiate a object store client
+                # Instantiate an object store client
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8c227c and c664db5.

📒 Files selected for processing (1)
  • src/nat/builder/workflow_builder.py (17 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

src/**/*.py: All importable Python code must live under src/
All public APIs in src/ require Python 3.11+ type hints on parameters and return values; prefer typing/collections.abc abstractions; use typing.Annotated when useful
Provide Google-style docstrings for every public module, class, function, and CLI command; first line concise with a period; surround code entities with backticks

Files:

  • src/nat/builder/workflow_builder.py
src/nat/**/*

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Core functionality under src/nat should prioritize backward compatibility when changed

Files:

  • src/nat/builder/workflow_builder.py

⚙️ CodeRabbit configuration file

This directory contains the core functionality of the toolkit. Changes should prioritize backward compatibility.

Files:

  • src/nat/builder/workflow_builder.py
**/*.py

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.py: Follow PEP 8/20 style; format with yapf (column_limit=120) and use 4-space indentation; end files with a single newline
Run ruff (ruff check --fix) per pyproject.toml; fix warnings unless explicitly ignored; ruff is linter-only
Use snake_case for functions/variables, PascalCase for classes, and UPPER_CASE for constants
Treat pyright warnings as errors during development
Exception handling: preserve stack traces and avoid duplicate logging
When re-raising exceptions, use bare raise and log with logger.error(), not logger.exception()
When catching and not re-raising, log with logger.exception() to capture stack trace
Validate and sanitize all user input; prefer httpx with SSL verification and follow OWASP Top‑10
Use async/await for I/O-bound work; profile CPU-heavy paths with cProfile/mprof; cache with functools.lru_cache or external cache; leverage NumPy vectorization when beneficial

**/*.py: Programmatic use: create TestLLMConfig(response_seq=[...], delay_ms=...), add with builder.add_llm("", cfg).
When retrieving the test LLM wrapper, use builder.get_llm(name, wrapper_type=LLMFrameworkEnum.) and call the framework’s method (e.g., ainvoke, achat, call).

Files:

  • src/nat/builder/workflow_builder.py
**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{py,sh,md,yml,yaml,toml,ini,json,ipynb,txt,rst}: Every file must start with the standard SPDX Apache-2.0 header; keep copyright years up‑to‑date
All source files must include the SPDX Apache‑2.0 header; do not bypass CI header checks

Files:

  • src/nat/builder/workflow_builder.py
**/*.{py,md}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Never hard‑code version numbers in code or docs; versions are derived by setuptools‑scm

Files:

  • src/nat/builder/workflow_builder.py
**/*.{py,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/nat-test-llm.mdc)

**/*.{py,yaml,yml}: Configure response_seq as a list of strings; values cycle per call, and [] yields an empty string.
Configure delay_ms to inject per-call artificial latency in milliseconds for nat_test_llm.

Files:

  • src/nat/builder/workflow_builder.py
**/*

⚙️ CodeRabbit configuration file

**/*: # Code Review Instructions

  • Ensure the code follows best practices and coding standards. - For Python code, follow
    PEP 20 and
    PEP 8 for style guidelines.
  • Check for security vulnerabilities and potential issues. - Python methods should use type hints for all parameters and return values.
    Example:
    def my_function(param1: int, param2: str) -> bool:
        pass
  • For Python exception handling, ensure proper stack trace preservation:
    • When re-raising exceptions: use bare raise statements to maintain the original stack trace,
      and use logger.error() (not logger.exception()) to avoid duplicate stack trace output.
    • When catching and logging exceptions without re-raising: always use logger.exception()
      to capture the full stack trace information.

Documentation Review Instructions - Verify that documentation and comments are clear and comprehensive. - Verify that the documentation doesn't contain any TODOs, FIXMEs or placeholder text like "lorem ipsum". - Verify that the documentation doesn't contain any offensive or outdated terms. - Verify that documentation and comments are free of spelling mistakes, ensure the documentation doesn't contain any

words listed in the ci/vale/styles/config/vocabularies/nat/reject.txt file, words that might appear to be
spelling mistakes but are listed in the ci/vale/styles/config/vocabularies/nat/accept.txt file are OK.

Misc. - All code (except .mdc files that contain Cursor rules) should be licensed under the Apache License 2.0,

and should contain an Apache License 2.0 header comment at the top of each file.

  • Confirm that copyright years are up-to date whenever a file is changed.

Files:

  • src/nat/builder/workflow_builder.py
🧠 Learnings (7)
📚 Learning: 2025-08-28T14:30:12.178Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/data_models/function.py:39-45
Timestamp: 2025-08-28T14:30:12.178Z
Learning: In the NAT codebase, stricter validation of function names (like checking for empty/whitespace-only strings) in FunctionGroupBaseConfig.validate_expose is unnecessary because workflow parsing and validation already handle these validation concerns at a different layer in the system.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T14:04:37.688Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: examples/object_store/user_report/configs/config_mysql.yml:38-48
Timestamp: 2025-08-28T14:04:37.688Z
Learning: In FunctionGroupBaseConfig, the expose field is defined with `expose: list[str] = Field(default_factory=list, ...)`, which means it defaults to an empty list rather than None. The get_exposed_functions() method directly accesses self._config.expose without None checks because it's guaranteed to be a list.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T14:29:34.897Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/workflow_builder.py:373-410
Timestamp: 2025-08-28T14:29:34.897Z
Learning: ComponentRef objects (like FunctionGroupRef) automatically convert to strings when used as dictionary keys in Python, so explicit str() conversions are not required when using them as dict keys.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T23:22:41.742Z
Learnt from: CR
PR: NVIDIA/NeMo-Agent-Toolkit#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-08-28T23:22:41.742Z
Learning: Applies to **/*.py : When re-raising exceptions, use bare `raise` and log with logger.error(), not logger.exception()

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T20:03:37.650Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/front_ends/mcp/mcp_front_end_plugin_worker.py:0-0
Timestamp: 2025-08-28T20:03:37.650Z
Learning: Function groups in NAT automatically namespace their function names with the group name (e.g., "user_report.get", "user_report.put"), making function name collisions impossible by design. No collision detection is needed when merging functions from function groups.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are sibling classes that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other and the order doesn't matter for classification purposes.

Applied to files:

  • src/nat/builder/workflow_builder.py
📚 Learning: 2025-08-28T14:28:34.779Z
Learnt from: willkill07
PR: NVIDIA/NeMo-Agent-Toolkit#684
File: src/nat/builder/component_utils.py:110-114
Timestamp: 2025-08-28T14:28:34.779Z
Learning: FunctionGroupBaseConfig and FunctionBaseConfig are separate class hierarchies that both inherit from (TypedBaseModel, BaseModelRegistryTag). FunctionGroupBaseConfig does not inherit from FunctionBaseConfig, so isinstance checks between them won't interfere with each other.

Applied to files:

  • src/nat/builder/workflow_builder.py
🧬 Code graph analysis (1)
src/nat/builder/workflow_builder.py (9)
src/nat/builder/function.py (5)
  • FunctionGroup (350-512)
  • get_included_functions (481-501)
  • add_function (371-411)
  • Function (45-300)
  • get_accessible_functions (437-459)
src/nat/data_models/component_ref.py (12)
  • FunctionGroupRef (105-113)
  • FunctionRef (94-102)
  • component_group (69-76)
  • component_group (90-91)
  • component_group (101-102)
  • component_group (112-113)
  • component_group (123-124)
  • component_group (134-135)
  • component_group (145-146)
  • component_group (156-157)
  • component_group (167-168)
  • component_group (178-179)
src/nat/data_models/function.py (2)
  • FunctionGroupBaseConfig (30-55)
  • FunctionBaseConfig (26-27)
src/nat/data_models/function_dependencies.py (5)
  • FunctionDependencies (21-80)
  • add_function (61-62)
  • add_function_group (64-65)
  • add_object_store (76-77)
  • add_retriever (79-80)
src/nat/data_models/config.py (1)
  • Config (239-426)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (11)
  • get_function_group (151-157)
  • add_function (131-133)
  • add_function_group (147-149)
  • get_function (135-141)
  • get_function_config (143-145)
  • get_function_group_config (159-161)
  • get_function_group_dependencies (288-290)
  • get_tools (179-181)
  • get_tool (183-185)
  • add_object_store (258-260)
  • add_retriever (241-243)
src/nat/builder/builder.py (11)
  • get_function_group (79-80)
  • add_function (67-68)
  • add_function_group (71-72)
  • get_function (75-76)
  • get_function_config (90-91)
  • get_function_group_config (94-95)
  • get_function_group_dependencies (280-281)
  • get_tools (110-113)
  • get_tool (116-117)
  • add_object_store (157-158)
  • add_retriever (217-218)
src/nat/cli/type_registry.py (2)
  • get_function_group (517-533)
  • get_function (487-493)
src/nat/profiler/decorators/framework_wrapper.py (1)
  • chain_wrapped_build_fn (108-128)
🪛 Ruff (0.12.2)
src/nat/builder/workflow_builder.py

415-416: Prefer TypeError exception for invalid type

(TRY004)


415-416: Avoid specifying long messages outside the exception class

(TRY003)


426-426: Avoid specifying long messages outside the exception class

(TRY003)


440-440: Avoid specifying long messages outside the exception class

(TRY003)


451-451: Avoid specifying long messages outside the exception class

(TRY003)


464-464: Avoid specifying long messages outside the exception class

(TRY003)


473-473: Avoid specifying long messages outside the exception class

(TRY003)


482-482: Avoid specifying long messages outside the exception class

(TRY003)


491-491: Avoid specifying long messages outside the exception class

(TRY003)


545-545: Avoid specifying long messages outside the exception class

(TRY003)


550-550: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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 (12)
src/nat/builder/workflow_builder.py (12)

19-19: LGTM: imports add required typing and FunctionGroup symbols.

Also applies to: 21-21, 36-36, 48-48, 59-59


93-97: LGTM: ConfiguredFunctionGroup dataclass is clear and consistent with others.


159-159: LGTM: state added for function groups and deps.

Also applies to: 176-176, 178-178


265-266: LGTM: wiring function_groups into Config and Workflow.

Also applies to: 300-301


460-466: LGTM: FunctionRef normalization in get_function.


469-476: LGTM: FunctionGroupRef normalization in get_function_group.


479-485: LGTM: FunctionRef normalization in get_function_config.


487-494: LGTM: FunctionGroupRef normalization in get_function_group_config.


1123-1126: LGTM: ChildBuilder delegates add_function_group.


1136-1144: LGTM: ChildBuilder get_function_group tracks dependency.


1149-1152: LGTM: ChildBuilder get_function_group_config delegation.


1308-1311: LGTM: ChildBuilder forwards get_function_group_dependencies.

Copy link
Contributor

@lvojtku lvojtku left a comment

Choose a reason for hiding this comment

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

added some comments but approved.

Copy link
Member

@zhongxuanwang-nv zhongxuanwang-nv left a comment

Choose a reason for hiding this comment

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

I think I can't find any other things at the moment (I didn't go through this fully either). Thanks Will! I did also learn some interesting python tricks through your code 😂

willkill07 and others added 8 commits September 16, 2025 22:01
Signed-off-by: Will Killian <wkillian@nvidia.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Will Killian <2007799+willkill07@users.noreply.github.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
Signed-off-by: Will Killian <wkillian@nvidia.com>
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 (1)
tests/nat/builder/test_builder.py (1)

893-934: Fix shadowed variable and tautological assertion in test_built_config.

workflow_config is reused, causing assert workflow_config.workflow == workflow_config.workflow to be a no-op.

Apply:

-    general_config = GeneralConfig()
-    function_config = FunctionReturningFunctionConfig()
-    workflow_config = FunctionReturningFunctionConfig()
+    general_config = GeneralConfig()
+    function_config = FunctionReturningFunctionConfig()
+    workflow_cfg = FunctionReturningFunctionConfig()
@@
-        await builder.set_workflow(workflow_config)
+        await builder.set_workflow(workflow_cfg)
@@
-        workflow_config = workflow.config
-
-        assert workflow_config.general == general_config
-        assert workflow_config.functions == {"function1": function_config}
-        assert workflow_config.workflow == workflow_config.workflow
-        assert workflow_config.llms == {"llm1": llm_config}
-        assert workflow_config.embedders == {"embedder1": embedder_config}
-        assert workflow_config.memory == {"memory1": memory_config}
-        assert workflow_config.retrievers == {"retriever1": retriever_config}
-        assert workflow_config.object_stores == {"object_store1": object_store_config}
-        assert workflow_config.ttc_strategies == {"ttc_strategy": ttc_config}
+        built_cfg = workflow.config
+
+        assert built_cfg.general == general_config
+        assert built_cfg.functions == {"function1": function_config}
+        assert built_cfg.workflow == workflow_cfg
+        assert built_cfg.llms == {"llm1": llm_config}
+        assert built_cfg.embedders == {"embedder1": embedder_config}
+        assert built_cfg.memory == {"memory1": memory_config}
+        assert built_cfg.retrievers == {"retriever1": retriever_config}
+        assert built_cfg.object_stores == {"object_store1": object_store_config}
+        assert built_cfg.ttc_strategies == {"ttc_strategy": ttc_config}
♻️ Duplicate comments (19)
src/nat/data_models/function_dependencies.py (1)

33-35: Deterministic JSON: sort all set serializers.

Returning list(v) yields nondeterministic order; sort for stable outputs and easier testing/caching. Apply to all serializers for consistency.

     @field_serializer("functions", when_used="json")
     def serialize_functions(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

     @field_serializer("function_groups", when_used="json")
     def serialize_function_groups(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

     @field_serializer("llms", when_used="json")
     def serialize_llms(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

     @field_serializer("embedders", when_used="json")
     def serialize_embedders(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

     @field_serializer("memory_clients", when_used="json")
     def serialize_memory_clients(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

     @field_serializer("object_stores", when_used="json")
     def serialize_object_stores(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

     @field_serializer("retrievers", when_used="json")
     def serialize_retrievers(self, v: set[str]) -> list[str]:
-        return list(v)
+        return sorted(v)

Also applies to: 37-40, 41-59

examples/object_store/user_report/configs/config_s3.yml (2)

37-41: Duplicate object_store setting — remove the front_end one.

Group-level object_store is authoritative; keeping general.front_end.object_store risks schema validation failures.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']

73-74: Ensure tests cover tool_names group expansion and ordering.

Add/verify a test that [user_report] expands to ["user_report.get","user_report.put","user_report.update","user_report.delete"] in that order.

examples/object_store/user_report/configs/config_mem.yml (1)

70-71: Test group expansion/order for tool_names.

Same as other configs: assert expansion and exact order.

src/nat/builder/component_utils.py (1)

261-263: Back-compat: guard missing attrs in older Configs.

Direct access to config.function_groups breaks older configs. Use getattr(..., {}) consistently.

-    total_node_count = (len(config.embedders) + len(config.functions) + len(config.function_groups) + len(config.llms) +
-                        len(config.memory) + len(config.object_stores) + len(config.retrievers) +
-                        len(config.ttc_strategies) + len(config.authentication) + 1)  # +1 for the workflow
+    total_node_count = (
+        len(getattr(config, "embedders", {}))
+        + len(getattr(config, "functions", {}))
+        + len(getattr(config, "function_groups", {}))
+        + len(getattr(config, "llms", {}))
+        + len(getattr(config, "memory", {}))
+        + len(getattr(config, "object_stores", {}))
+        + len(getattr(config, "retrievers", {}))
+        + len(getattr(config, "ttc_strategies", {}))
+        + len(getattr(config, "authentication", {}))
+        + 1  # +1 for the workflow
+    )

Additionally, harden config_to_dependency_objects similarly (outside changed hunk):

# In config_to_dependency_objects(...)
component_dict = getattr(config, group.value, {})
src/nat/tool/github_tools.py (2)

195-203: Fix labels filter encoding for GitHub issues API.

GitHub expects a comma-separated labels string, not repeated params.

         async def get_issue(issues_list: GithubGetIssueModelList) -> str:
             url = f"https://api.github.com/repos/{config.repo_name}/issues"
             results = []
             for issue in issues_list.filter_parameters:
-                params = issue.model_dump(exclude_unset=True, exclude_none=True)
+                params = issue.model_dump(exclude_unset=True, exclude_none=True)
+                if isinstance(params.get("labels"), list):
+                    params["labels"] = ",".join(params["labels"])
                 response = await client.get(url, params=params)
                 response.raise_for_status()
                 results.append(response.json())
             return json.dumps(results)

424-431: Build correct raw.githubusercontent.com URL (remove refs/heads).

Current URL is invalid and breaks file retrieval.

-            # The following URL is the raw URL of the file. refs/heads/ always points to the top commit of the branch
-            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/refs/heads/{file_metadata.file_path}"
+            # Build raw URL: https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}
+            raw_url = f"https://raw.githubusercontent.com/{file_metadata.repo_path}/{file_metadata.file_path}"
             try:
                 response = await client.get(raw_url)
                 response.raise_for_status()
src/nat/data_models/function.py (1)

44-50: Preserve caller order for include/exclude (don’t sort).

Sorting alters user-specified exposure order and can change tool resolution. Keep uniqueness check only.

     @field_validator("include", "exclude")
     @classmethod
     def _validate_fields_include_exclude(cls, value: list[str]) -> list[str]:
         if len(set(value)) != len(value):
             raise ValueError("Function names must be unique")
-        return sorted(value)
+        return value
src/nat/agent/tool_calling_agent/register.py (1)

39-40: Clarify tool_names description to mention groups.

Matches the new accepted types and avoids confusion.

-    tool_names: list[FunctionRef | FunctionGroupRef] = Field(
-        default_factory=list, description="The list of tools to provide to the tool calling agent.")
+    tool_names: list[FunctionRef | FunctionGroupRef] = Field(
+        default_factory=list,
+        description="The list of functions and/or function groups to provide to the tool calling agent.",
+    )
tests/nat/agent/test_reasoning_agent.py (3)

141-146: Annotate mutable class attrs with ClassVar (RUF012).

Avoids instance-level mutation and silences ruff.

-    class FakeDeps:
-        functions = {"SomeTool"}
-        function_groups = set()
+    from typing import ClassVar
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"SomeTool"}
+        function_groups: ClassVar[set[str]] = set()

If from typing import ClassVar already exists at file top, drop the inline import here.


305-310: Repeat ClassVar annotations here.

-    class FakeDeps:
-        functions = {"ToolA", "ToolB"}
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = {"ToolA", "ToolB"}
+        function_groups: ClassVar[set[str]] = set()

380-385: Repeat ClassVar annotations for empty-set case.

-    class FakeDeps:
-        functions = set()
-        function_groups = set()
+    class FakeDeps:
+        functions: ClassVar[set[str]] = set()
+        function_groups: ClassVar[set[str]] = set()
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (5)

20-20: Import missing types to match Builder API.

Aligns signatures and quiets type-checkers.

-from collections.abc import Sequence
+from collections.abc import Sequence
+from nat.builder.framework_enum import LLMFrameworkEnum
+from nat.data_models.component_ref import FunctionRef, FunctionGroupRef

147-150: Widen add_function_group signature (match abstract base expectations across codebase).

-    async def add_function_group(self, name: str, config: FunctionGroupBaseConfig) -> FunctionGroup:
+    async def add_function_group(self, name: str | FunctionGroupRef, config: FunctionGroupBaseConfig) -> FunctionGroup:

151-158: Accept FunctionGroupRef and index mocks by str(name); return spec’ed mock.

-    def get_function_group(self, name: str) -> FunctionGroup:
+    def get_function_group(self, name: str | FunctionGroupRef) -> FunctionGroup:
         """Return a mock function group if one is configured."""
-        if name in self._mocks:
-            mock_fn_group = MagicMock(spec=FunctionGroup)
-            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[name])
+        key = str(name)
+        if key in self._mocks:
+            mock_fn_group = MagicMock(spec=FunctionGroup)
+            mock_fn_group.ainvoke = AsyncMock(return_value=self._mocks[key])
             return mock_fn_group
         raise ValueError(f"Function group '{name}' not mocked. Use mock_function_group() to add it.")

159-162: Match config getter signature with wider ref type.

-    def get_function_group_config(self, name: str) -> FunctionGroupBaseConfig:
+    def get_function_group_config(self, name: str | FunctionGroupRef) -> FunctionGroupBaseConfig:
         """Mock implementation."""
         return FunctionGroupBaseConfig()

179-182: Broaden get_tools signature to accept FunctionRefs/GroupRefs; type wrapper.

-    def get_tools(self, tool_names: Sequence[str], wrapper_type):
+    def get_tools(
+        self,
+        tool_names: Sequence[str | FunctionRef | FunctionGroupRef],
+        wrapper_type: LLMFrameworkEnum | str,
+    ) -> list[typing.Any]:
         """Mock implementation."""
-        return []
+        return []
docs/source/workflows/function-groups.md (1)

52-54: Fix Builder import path in snippet.

Builder lives in nat.builder.builder.

-from nat.builder.workflow_builder import Builder
+from nat.builder.builder import Builder
src/nat/builder/workflow_builder.py (1)

1165-1175: Dependency tracking bug for FunctionGroupRef inputs.

Membership checks use raw values; FunctionGroupRef won't match string keys, so group deps aren’t recorded.

@@
-        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
-        for tool_name in tool_names:
-            if tool_name in self._workflow_builder._function_groups:
-                self._dependencies.add_function_group(tool_name)
-            else:
-                self._dependencies.add_function(tool_name)
+        tools = self._workflow_builder.get_tools(tool_names, wrapper_type)
+        for tool_name in tool_names:
+            key = str(tool_name)
+            if key in self._workflow_builder._function_groups:
+                self._dependencies.add_function_group(key)
+            else:
+                self._dependencies.add_function(key)
         return tools
🧹 Nitpick comments (25)
src/nat/data_models/function_dependencies.py (1)

64-66: Remove unnecessary pylint disable; add a brief docstring.

The attribute exists; the suppression is noise. Consider a one-liner docstring to meet public API docs.

-    def add_function_group(self, function_group: str):
-        self.function_groups.add(function_group)  # pylint: disable=no-member
+    def add_function_group(self, function_group: str):
+        """Add a function group dependency by name."""
+        self.function_groups.add(function_group)
examples/object_store/user_report/configs/config_mem.yml (1)

34-38: Remove duplicate object_store from front_end; group-level is the source of truth.

 general:
   front_end:
     _type: fastapi
-    object_store: report_object_store
     cors:
       allow_origins: ['*']
src/nat/tool/github_tools.py (2)

176-181: Send recommended GitHub headers (API version).

Add X-GitHub-Api-Version for better forward compatibility.

     headers = {
         "Authorization": f"Bearer {token}",
         "Accept": "application/vnd.github+json",
         "User-Agent": "NeMo-Agent-Toolkit",
+        "X-GitHub-Api-Version": "2022-11-28",
     }

184-216: Add concise Google-style docstrings to exposed handlers.

These are public surface via the function group; add one-liners for clarity.

         # Issues
         async def create_issue(issues_list: GithubCreateIssueModelList) -> str:
+            """Create issues via POST /repos/{owner}/{repo}/issues. Returns JSON string."""
@@
         async def get_issue(issues_list: GithubGetIssueModelList) -> str:
+            """List issues via GET /repos/{owner}/{repo}/issues. Returns JSON string."""
@@
         async def update_issue(issues_list: GithubUpdateIssueModelList) -> str:
+            """Update issues via PATCH /repos/{owner}/{repo}/issues/{number}. Returns JSON string."""
@@
         # Pull requests
         async def create_pull(pull_list: GithubCreatePullList) -> str:
+            """Create PRs and optionally assign/request reviews. Returns JSON string."""
@@
         async def get_pull(pull_list: GithubGetPullsModelList) -> str:
+            """List pull requests via GET /repos/{owner}/{repo}/pulls. Returns JSON string."""
@@
         # Commits (commit updated files)
         async def commit(updated_file_list: GithubCommitCodeModelList) -> str:
+            """Commit local files: blob → tree → commit → update ref. Returns JSON string."""

Also applies to: 217-251, 252-262, 263-339

src/nat/agent/tool_calling_agent/register.py (1)

114-114: Fix copy/paste log message.

Log refers to react_agent.

-        logger.debug("%s Cleaning up react_agent workflow.", AGENT_LOG_PREFIX)
+        logger.debug("%s Cleaning up tool_calling_agent workflow.", AGENT_LOG_PREFIX)
packages/nvidia_nat_test/src/nat/test/tool_test_runner.py (1)

288-291: Optional: mirror WorkflowBuilder and accept FunctionGroupRef here too.

Keeps parity with concrete implementation.

-    def get_function_group_dependencies(self, fn_name: str) -> FunctionDependencies:
+    def get_function_group_dependencies(self, fn_name: str | FunctionGroupRef) -> FunctionDependencies:
         """Mock implementation."""
         return FunctionDependencies()
docs/source/workflows/function-groups.md (2)

20-21: Use official first mention: “NVIDIA NeMo Agent toolkit”.

-Function groups let you package multiple related functions together so they can share configuration, context, and resources within the NeMo Agent toolkit.
+Function groups let you package multiple related functions together so they can share configuration, context, and resources within the NVIDIA NeMo Agent toolkit.

75-77: Consider replacing placeholder context manager with a real one (or add a note).

Prevents readers from copying a non-existent symbol.

-    async with SomeAsyncContextManager() as context:
+    # Example: share resources across functions
+    from contextlib import AsyncExitStack
+    async with AsyncExitStack() as context:
examples/object_store/user_report/configs/config_mysql.yml (1)

74-74: Provide a test stub variant for CI.

Repo guidelines recommend nat_test_llm in YAML. Keep this example as-is, but add a sibling test config (e.g., config_mysql.test.yml) switching llms.nim_llm to nat_test_llm with response_seq/delay_ms for deterministic tests.

examples/object_store/user_report/src/nat_user_report/user_report_tools.py (3)

46-47: Add explicit return type and import AsyncIterator.

Public API requires return annotations.

@@
-@register_function_group(config_type=UserReportConfig)
-async def user_report_group(config: UserReportConfig, builder: Builder):
+from typing import AsyncIterator
+@register_function_group(config_type=UserReportConfig)
+async def user_report_group(config: UserReportConfig, builder: Builder) -> AsyncIterator[FunctionGroup]:

68-90: Validate report JSON before storing with application/json.

Currently any string gets stored with JSON content-type; validate or adjust content-type.

@@
-    async def put(report: str, user_id: str, date: str | None = None) -> str:
+    async def put(report: str, user_id: str, date: str | None = None) -> str:
         """Store a new user report in the object store."""
         date = date or "latest"
         key = f"reports/{user_id}/{date}.json"
         logger.info("Putting new report into %s for user %s with date %s", key, user_id, date)
         try:
-            await object_store.put_object(key=key,
-                                          item=ObjectStoreItem(data=report.encode("utf-8"),
-                                                               content_type="application/json"))
+            # Ensure valid JSON, else return a clear error
+            import json
+            json.loads(report)
+            await object_store.put_object(
+                key=key,
+                item=ObjectStoreItem(data=report.encode("utf-8"), content_type="application/json"),
+            )
             return f"User report for {user_id} with date {date} added successfully"
         except KeyAlreadyExistsError:
             return f"User report for {user_id} with date {date} already exists"
@@
-    async def update(report: str, user_id: str, date: str | None = None) -> str:
+    async def update(report: str, user_id: str, date: str | None = None) -> str:
         """Update or create a user report in the object store."""
         date = date or "latest"
         key = f"reports/{user_id}/{date}.json"
         logger.info("Update or insert report into %s for user %s with date %s", key, user_id, date)
-        await object_store.upsert_object(key=key,
-                                         item=ObjectStoreItem(data=report.encode("utf-8"),
-                                                              content_type="application/json"))
+        import json
+        json.loads(report)
+        await object_store.upsert_object(
+            key=key,
+            item=ObjectStoreItem(data=report.encode("utf-8"), content_type="application/json"),
+        )
         return f"User report for {user_id} with date {date} updated"

60-67: Avoid logging full PII at info level.

Consider masking user_id or using debug to reduce PII exposure in examples.

src/nat/builder/function.py (2)

370-377: Add return annotation and tighten converter typing.

Public API should be explicit; also prefer concrete callable signatures.

-    def add_function(self,
+    def add_function(self,
                      name: str,
                      fn: Callable,
                      *,
                      input_schema: type[BaseModel] | None = None,
                      description: str | None = None,
-                     converters: list[Callable] | None = None):
+                     converters: list[Callable[[typing.Any], typing.Any]] | None = None) -> None:

400-406: Precompile name regex and shorten error strings (fix TRY003 and micro-opt).

Reduces per-call regex cost and silences Ruff on long messages.

@@
-        if not name.strip():
-            raise ValueError("Function name cannot be empty or blank")
-        if not re.match(r"^[a-zA-Z0-9_-]+$", name):
-            raise ValueError(f"Function name can only contain letters, numbers, underscores, and hyphens: {name}")
-        if name in self._functions:
-            raise ValueError(f"Function {name} already exists in function group {self._instance_name}")
+        NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
+        name = name.strip()
+        if not name:
+            raise ValueError("Empty function name.")
+        if not NAME_RE.match(name):
+            raise ValueError(f"Invalid function name: {name!r}.")
+        if name in self._functions:
+            raise ValueError(f"Duplicate function: {name!r}.")
src/nat/cli/type_registry.py (2)

499-516: Shorten duplicate-registration error (fix TRY003).

Concise messages avoid Ruff warnings.

-        if (registration.config_type in self._registered_function_groups):
-            raise ValueError(
-                f"A function group with the same config type `{registration.config_type}` has already been "
-                "registered.")
+        if registration.config_type in self._registered_function_groups:
+            raise ValueError("Function group already registered.")

517-541: Shorten not-found error (fix TRY003).

Keep messages compact and actionable.

-        except KeyError as err:
-            raise KeyError(f"Could not find a registered function group for config `{config_type}`. "
-                           f"Registered configs: {set(self._registered_function_groups.keys())}") from err
+        except KeyError as err:
+            raise KeyError("No registered function group for config.") from err
src/nat/builder/workflow_builder.py (4)

382-419: Seed group dependencies under the instance key, not config.type.

Avoids stray entries and keeps keys consistent with lookups.

@@
-        self.current_function_group_building = config.type
-        # Empty set of dependencies for the current function group
-        self.function_group_dependencies[config.type] = FunctionDependencies()
+        self.current_function_group_building = config.type
+        # Track dependencies under the instance key
+        self.function_group_dependencies[name] = FunctionDependencies()
@@
-        self.function_group_dependencies[name] = inner_builder.dependencies
+        self.function_group_dependencies[name] = inner_builder.dependencies

535-569: Handle duplicate function exposure when a group and a member are both requested.

If tool_names contains both a group and one of its functions, duplicate tools may be returned. Consider de-duping by fn_name.

@@
-        tools = []
+        tools = []
+        seen_fns: set[str] = set()
@@
-            for fn_name, fn_instance in current_function_group.instance.get_accessible_functions().items():
+            for fn_name, fn_instance in current_function_group.instance.get_accessible_functions().items():
+                if fn_name in seen_fns:
+                    continue
                 try:
                     # Wrap in the correct wrapper and add to tools list
                     tools.append(tool_wrapper_reg.build_fn(fn_name, fn_instance, self))
+                    seen_fns.add(fn_name)

570-586: Add return annotation to get_tool.

Aligns with Builder interface and repo rules.

-    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str):
+    def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:

1178-1185: Normalize fn_name before recording dependency.

Ensures FunctionRef keys match the string map.

@@
-        fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
-
-        self._dependencies.add_function(fn_name)
+        fn = self._workflow_builder.get_tool(fn_name, wrapper_type)
+        self._dependencies.add_function(str(fn_name))
 
         return fn
tests/nat/builder/test_builder.py (5)

286-417: Silence TRY003 in tests and fix unused-arg to satisfy Ruff.

  • Several intentional “descriptive” raises trip TRY003; prefer a file-level ignore for tests.
  • register_failing_function_group doesn’t use config; rename to _config to fix ARG001.

Apply:

@@
-import logging
+import logging
+# ruff: noqa: TRY003  # allow descriptive exception messages in this test module

And:

-    async def register_failing_function_group(config: FailingFunctionGroupConfig, _builder: Builder):
+    async def register_failing_function_group(_config: FailingFunctionGroupConfig, _builder: Builder):

1374-1390: Align fixtures with project guidelines (name=..., fixture_ prefix).

Rename functions and set name=... to satisfy the stated fixture conventions.

-@pytest.fixture
-def caplog_fixture(caplog):
+@pytest.fixture(name="caplog_fixture")
+def fixture_caplog_fixture(caplog):
@@
-@pytest.fixture
-def mock_component_data():
+@pytest.fixture(name="mock_component_data")
+def fixture_mock_component_data():

1365-1369: Prefer isinstance over issubclass(type(...), ...).

More idiomatic and clearer.

-        assert issubclass(type(exporter1_instance), BaseExporter)
+        assert isinstance(exporter1_instance, BaseExporter)

1054-1065: Avoid reaching into builder._functions in tests when possible.

These assertions depend on private internals; prefer public APIs (e.g., group.get_included_functions()/get_all_functions()) for stability. Keep as-is if you intentionally validate registry internals.

Also applies to: 1162-1168


1195-1209: Brittle message matching in add_function validation tests.

These rely on exact ValueError strings. If implementation text diverges, tests will fail needlessly. Consider relaxing to regex fragments or dropping match.

@willkill07
Copy link
Member Author

/merge

@rapids-bot rapids-bot bot merged commit 76df4cd into NVIDIA:develop Sep 17, 2025
17 checks passed
@willkill07 willkill07 deleted the wkk_function-groups 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

breaking Breaking change feature request New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants