Skip to content

Commit 13dc6bc

Browse files
committed
Merge branch 'develop' of github.com:NVIDIA/NeMo-Agent-Toolkit into feat/tool-error-observability
2 parents d7018a9 + 7629460 commit 13dc6bc

147 files changed

Lines changed: 9071 additions & 1324 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ci/scripts/gitlab/artifactory_upload.sh

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ if [[ -z "${URM_USER}" || -z "${URM_API_KEY}" ]]; then
5454
exit 1
5555
fi
5656

57-
if [[ -z "${AIQ_ARTIFACTORY_URL}" || -z "${AIQ_ARTIFACTORY_NAME}" ]]; then
58-
echo "Error: AIQ_ARTIFACTORY_URL or AIQ_ARTIFACTORY_NAME is not set. Exiting."
57+
if [[ -z "${NAT_ARTIFACTORY_URL}" || -z "${NAT_ARTIFACTORY_NAME}" ]]; then
58+
echo "Error: NAT_ARTIFACTORY_URL or NAT_ARTIFACTORY_NAME is not set. Exiting."
5959
exit 1
6060
fi
6161

@@ -113,11 +113,11 @@ if [[ "${UPLOAD_TO_ARTIFACTORY}" == "true" ]]; then
113113
# as this is an already established path in artifactory
114114
RELATIVE_PATH="${WHEEL_FILE#${WHEELS_BASE_DIR}/}"
115115
RELATIVE_PATH=$(echo "${RELATIVE_PATH}" | sed -e 's|^nvidia-nat/|aiqtoolkit/|' | sed -e 's|^nat/|aiqtoolkit/|')
116-
ARTIFACTORY_PATH="${AIQ_ARTIFACTORY_NAME}/${RELATIVE_PATH}"
116+
ARTIFACTORY_PATH="${NAT_ARTIFACTORY_NAME}/${RELATIVE_PATH}"
117117

118118
echo "Uploading ${WHEEL_FILE} to ${ARTIFACTORY_PATH}..."
119119

120-
CI=true jf rt u --fail-no-op --url="${AIQ_ARTIFACTORY_URL}" \
120+
CI=true jf rt u --fail-no-op --url="${NAT_ARTIFACTORY_URL}" \
121121
--user="${URM_USER}" --password="${URM_API_KEY}" \
122122
--flat=false "${WHEEL_FILE}" "${ARTIFACTORY_PATH}" \
123123
--target-props "arch=${NAT_ARCH};os=${NAT_OS};branch=${GIT_TAG};component_name=${ARTIFACTORY_COMPONENT_FIXED_NAME};version=${GIT_TAG};release_approver=${RELEASE_APPROVER};release_status=${RELEASE_STATUS}"
@@ -131,8 +131,8 @@ fi
131131

132132
# List Artifactory contents (disabled by default as the output is very verbose)
133133
if [[ "${LIST_ARTIFACTORY_CONTENTS}" == "true" ]]; then
134-
echo "Listing contents of Artifactory (${AIQ_ARTIFACTORY_NAME}):"
135-
CI=true jf rt s --url="${AIQ_ARTIFACTORY_URL}" \
134+
echo "Listing contents of Artifactory (${NAT_ARTIFACTORY_NAME}):"
135+
CI=true jf rt s --url="${NAT_ARTIFACTORY_URL}" \
136136
--user="${URM_USER}" --password="${URM_API_KEY}" \
137-
"${AIQ_ARTIFACTORY_NAME}/*/${GIT_TAG}/" --recursive
137+
"${NAT_ARTIFACTORY_NAME}/*/${GIT_TAG}/" --recursive
138138
fi

docs/source/extend/custom-components/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ MCP Server Worker <./mcp-server.md>
3232
Memory Provider <./memory.md>
3333
Object Store Provider <./object-store.md>
3434
Telemetry Exporter <./telemetry-exporters.md>
35+
Optimizer <./optimizer.md>
3536
Gated Fields <./gated-fields.md>
3637
Finetuning Harness <./finetuning.md>
3738
```
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
<!--
2+
SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
# Adding a Custom Optimizer
19+
20+
:::{note}
21+
We recommend reading the [Optimizer](../../improve-workflows/optimizer.md) guide before proceeding with this documentation.
22+
:::
23+
24+
NeMo Agent Toolkit provides a pluggable optimizer system for tuning workflow parameters and prompts. The built-in strategies include Optuna-based numeric optimization and a genetic algorithm (GA) for prompt optimization. You can add custom optimization strategies by implementing one of the optimizer base classes and registering it with the `@register_optimizer` decorator.
25+
26+
## Key Interfaces
27+
28+
* **Configuration Base Classes**
29+
- {py:class}`~nat.data_models.optimizer.OptimizerStrategyBaseConfig`: Base class that all optimizer strategy configuration models must extend. Provides an `enabled` field and integrates with the NeMo Agent Toolkit type registry.
30+
- {py:class}`~nat.data_models.optimizer.PromptOptimizationConfig`: Base for prompt optimization strategy configuration models. Adds `prompt_population_init_function` and `prompt_recombination_function` fields.
31+
- {py:class}`~nat.data_models.optimizer.OptunaParameterOptimizationConfig`: Built-in config for Optuna-based numeric parameter optimization.
32+
33+
* **Optimizer ABCs**
34+
- {py:class}`~nat.plugins.config_optimizer.prompts.base.BasePromptOptimizer`: Abstract base class for prompt optimization strategies. Requires implementing an async `run()` method that persists optimized prompts to disk; the in-memory config is left unchanged.
35+
- {py:class}`~nat.plugins.config_optimizer.parameters.base.BaseParameterOptimizer`: Abstract base class for parameter optimization strategies. Requires implementing an async `run()` method that returns an optimized `Config`.
36+
37+
* **Registration**
38+
- {py:deco}`~nat.cli.register_workflow.register_optimizer`: Decorator that registers an optimizer strategy with the global type registry so the optimizer runtime can resolve the strategy from the type of `cfg.optimizer.numeric` or `cfg.optimizer.prompt`.
39+
40+
## Adding a Custom Prompt Optimizer
41+
42+
### 1. Define a config class
43+
44+
Create a config class extending {py:class}`~nat.data_models.optimizer.PromptOptimizationConfig` with a unique `name`:
45+
46+
```python
47+
from pydantic import Field
48+
49+
from nat.data_models.optimizer import PromptOptimizationConfig
50+
51+
52+
class IterativeRefinementPromptConfig(PromptOptimizationConfig, name="iterative"):
53+
max_iterations: int = Field(default=20, description="Maximum refinement iterations.")
54+
candidates_per_iteration: int = Field(default=5, description="Number of candidate prompts to generate per iteration.")
55+
improvement_threshold: float = Field(default=0.01, description="Minimum score improvement to continue iterating.")
56+
```
57+
58+
### 2. Implement the Optimizer
59+
60+
Implement {py:class}`~nat.plugins.config_optimizer.prompts.base.BasePromptOptimizer`:
61+
62+
```python
63+
from nat.plugins.config_optimizer.prompts.base import BasePromptOptimizer
64+
from nat.data_models.config import Config
65+
from nat.data_models.optimizable import SearchSpace
66+
from nat.data_models.optimizer import OptimizerConfig, OptimizerRunConfig
67+
68+
69+
class IterativeRefinementPromptOptimizer(BasePromptOptimizer):
70+
71+
async def run(
72+
self,
73+
*,
74+
base_cfg: Config,
75+
full_space: dict[str, SearchSpace],
76+
optimizer_config: OptimizerConfig,
77+
opt_run_config: OptimizerRunConfig,
78+
) -> None:
79+
ir_config = optimizer_config.prompt # Your IterativeRefinementPromptConfig instance
80+
81+
# Extract prompt parameters from full_space
82+
prompt_space = {k: v for k, v in full_space.items() if v.is_prompt}
83+
if not prompt_space:
84+
return
85+
86+
# Implement your optimization loop here
87+
# Use ir_config.max_iterations, ir_config.candidates_per_iteration, etc.
88+
...
89+
```
90+
91+
The `run()` method receives:
92+
- `base_cfg`: The workflow configuration to optimize.
93+
- `full_space`: A dictionary of parameter names to {py:class}`~nat.data_models.optimizable.SearchSpace` definitions. Filter for `is_prompt=True` entries to find prompt parameters.
94+
- `optimizer_config`: The full {py:class}`~nat.data_models.optimizer.OptimizerConfig`. Access your strategy config via `optimizer_config.prompt`.
95+
- `opt_run_config`: Runtime parameters including dataset path, endpoint, and result JSON path.
96+
97+
### 3. Register the Optimizer
98+
99+
Use the {py:deco}`~nat.cli.register_workflow.register_optimizer` decorator to register your strategy:
100+
101+
```python
102+
from nat.cli.register_workflow import register_optimizer
103+
104+
105+
@register_optimizer(config_type=IterativeRefinementPromptConfig)
106+
async def register_iterative_prompt_optimizer(config: IterativeRefinementPromptConfig):
107+
yield IterativeRefinementPromptOptimizer()
108+
```
109+
110+
### 4. Import for Discovery
111+
112+
Import the registration function in your project's `register.py` to ensure it runs at startup:
113+
114+
<!-- path-check-skip-next-line -->
115+
```python
116+
from . import iterative_prompt_optimizer # noqa: F401 — triggers @register_optimizer
117+
```
118+
119+
### 5. Configure Programmatically
120+
121+
Custom strategy selection for `optimizer.prompt` is currently programmatic. After loading your workflow config, set `cfg.optimizer.prompt` to your custom config before calling `optimize_config`:
122+
123+
```python
124+
from nat.plugins.config_optimizer.optimizer_runtime import optimize_config
125+
from nat.data_models.optimizer import OptimizerRunConfig
126+
from nat.runtime.loader import load_config
127+
128+
cfg = load_config("workflow.yml")
129+
cfg.optimizer.prompt = IterativeRefinementPromptConfig(
130+
enabled=True,
131+
max_iterations=200,
132+
candidates_per_iteration=10,
133+
improvement_threshold=0.01,
134+
prompt_population_init_function="my_init_fn",
135+
)
136+
137+
await optimize_config(
138+
OptimizerRunConfig(
139+
config_file=cfg,
140+
dataset="dataset.json",
141+
result_json_path="$",
142+
)
143+
)
144+
```
145+
146+
## Adding a Custom Parameter Optimizer
147+
148+
The pattern is the same, but parameter optimizers extend {py:class}`~nat.plugins.config_optimizer.parameters.base.BaseParameterOptimizer` and return an optimized {py:class}`~nat.data_models.config.Config`:
149+
150+
### 1. Define a config class
151+
152+
```python
153+
from pydantic import Field
154+
155+
from nat.data_models.optimizer import OptimizerStrategyBaseConfig
156+
157+
158+
class RandomSearchConfig(OptimizerStrategyBaseConfig, name="random_search"):
159+
n_samples: int = Field(default=50, description="Number of random samples to evaluate.")
160+
```
161+
162+
### 2. Implement the Optimizer
163+
164+
```python
165+
from nat.plugins.config_optimizer.parameters.base import BaseParameterOptimizer
166+
from nat.data_models.config import Config
167+
from nat.data_models.optimizable import SearchSpace
168+
from nat.data_models.optimizer import OptimizerConfig, OptimizerRunConfig
169+
170+
171+
class RandomSearchOptimizer(BaseParameterOptimizer):
172+
173+
async def run(
174+
self,
175+
*,
176+
base_cfg: Config,
177+
full_space: dict[str, SearchSpace],
178+
optimizer_config: OptimizerConfig,
179+
opt_run_config: OptimizerRunConfig,
180+
) -> Config:
181+
rs_config = optimizer_config.numeric # Your RandomSearchConfig instance
182+
183+
# Filter out prompt parameters
184+
param_space = {k: v for k, v in full_space.items() if not v.is_prompt}
185+
if not param_space:
186+
return base_cfg
187+
188+
# Implement random search logic here
189+
# Return the best config found
190+
...
191+
return best_cfg
192+
```
193+
194+
### 3. Register and Configure
195+
196+
```python
197+
from nat.cli.register_workflow import register_optimizer
198+
199+
200+
@register_optimizer(config_type=RandomSearchConfig)
201+
async def register_random_search(config: RandomSearchConfig):
202+
yield RandomSearchOptimizer()
203+
```
204+
205+
Custom strategy selection for `optimizer.numeric` is also programmatic:
206+
207+
```python
208+
from nat.plugins.config_optimizer.optimizer_runtime import optimize_config
209+
from nat.data_models.optimizer import OptimizerRunConfig
210+
from nat.runtime.loader import load_config
211+
212+
cfg = load_config("workflow.yml")
213+
cfg.optimizer.numeric = RandomSearchConfig(enabled=True, n_samples=100)
214+
215+
await optimize_config(
216+
OptimizerRunConfig(
217+
config_file=cfg,
218+
dataset="dataset.json",
219+
result_json_path="$",
220+
)
221+
)
222+
```

docs/source/get-started/installation.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ The following [LLM](../build-workflows/llms/index.md) API providers are supporte
3030

3131
## Packages
3232

33-
To keep the library lightweight, many of the first-party plugins supported by NeMo Agent Toolkit are located in separate distribution packages. For example, the `nvidia-nat-langchain` distribution contains all the LangChain-specific and LangGraph-specific plugins, and the `nvidia-nat-mem0ai` distribution contains the Mem0-specific plugins.
33+
The default `nvidia-nat` install includes `nvidia-nat-core`. To keep the library lightweight, many first-party plugins (including the config optimizer) are optional. For example, the `nvidia-nat[config-optimizer]` extra adds parameter and prompt optimization. For example, the `nvidia-nat-langchain` distribution contains all the LangChain-specific and LangGraph-specific plugins, and the `nvidia-nat-mem0ai` distribution contains the Mem0-specific plugins.
3434

3535
To install these first-party plugin libraries, you can use the full distribution name (for example, `nvidia-nat-langchain`) or use the `nvidia-nat[langchain]` extra distribution. The following extras are supported:
3636

@@ -44,6 +44,7 @@ To install these first-party plugin libraries, you can use the full distribution
4444
- `nvidia-nat[mcp]` or `nvidia-nat-mcp` - [Model Context Protocol (MCP)](https://modelcontextprotocol.io/)
4545
- `nvidia-nat[mem0ai]` or `nvidia-nat-mem0ai` - [Mem0](https://mem0.ai/)
4646
- `nvidia-nat[mysql]` or `nvidia-nat-mysql` - [MySQL](https://www.mysql.com/)
47+
- `nvidia-nat[config-optimizer]` or `nvidia-nat-config-optimizer` - Parameter and prompt optimizer (required for `nat optimize`)
4748
- `nvidia-nat[openpipe-art]` or `nvidia-nat-openpipe-art` - [Agent Reinforcement Trainer](https://art.openpipe.ai/getting-started/about) Conflicts with `nvidia-nat[adk]` and `nvidia-nat[crewai]`.
4849
- `nvidia-nat[opentelemetry]` or `nvidia-nat-opentelemetry` - [OpenTelemetry](https://opentelemetry.io/)
4950
- `nvidia-nat[phoenix]` or `nvidia-nat-phoenix` - [Arize Phoenix](https://arize.com/docs/phoenix)

docs/source/improve-workflows/optimizer.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ limitations under the License.
1818

1919
This document provides a comprehensive overview of how to use the NeMo Agent Toolkit Optimizer to tune your NeMo Agent Toolkit [workflows](../build-workflows/about-building-workflows.md).
2020

21+
## Prerequisites
22+
23+
The optimizer is optional. Install it to use `nat optimize`: run `pip install nvidia-nat[config-optimizer]` or `pip install nvidia-nat-config-optimizer`. See the [Install Guide](../get-started/installation.md) for details.
24+
2125
## Introduction
2226

2327
### What is Parameter Optimization?
@@ -333,7 +337,6 @@ optimizer:
333337
prompt_recombination_function: "prompt_recombiner" # optional
334338
ga_population_size: 16
335339
ga_generations: 8
336-
ga_offspring_size: 12 # optional; defaults to pop_size - elitism
337340
ga_crossover_rate: 0.7
338341
ga_mutation_rate: 0.2
339342
ga_elitism: 2
@@ -367,7 +370,6 @@ This is the main configuration object for the optimizer.
367370
- `prompt.enabled: bool`: Enable GA-based prompt optimization. Defaults to `false`.
368371
- `prompt.ga_population_size: int`: Population size for GA prompt optimization. Larger populations increase diversity but cost more per generation. Defaults to `10`.
369372
- `prompt.ga_generations: int`: Number of generations for GA prompt optimization. Replaces `n_trials_prompt`. Defaults to `5`.
370-
- `prompt.ga_offspring_size: int | null`: Number of offspring produced per generation. If `null`, defaults to `ga_population_size - ga_elitism`.
371373
- `prompt.ga_crossover_rate: float`: Probability of recombination between two parents for each prompt parameter. Defaults to `0.7`.
372374
- `prompt.ga_mutation_rate: float`: Probability of mutating a child's prompt parameter using the LLM optimizer. Defaults to `0.1`.
373375
- `prompt.ga_elitism: int`: Number of elite individuals copied unchanged to the next generation. Defaults to `1`.
@@ -444,7 +446,7 @@ This section explains how the GA evolves prompt parameters when `do_prompt_optim
444446
- Selection: choose parents using `ga_selection_method` (`tournament` with `ga_tournament_size`, or `roulette`).
445447
- Crossover: with probability `ga_crossover_rate`, recombine two parent prompts for a parameter using `prompt_recombination_function` (if provided), otherwise pick from a parent.
446448
- Mutation: with probability `ga_mutation_rate`, apply `prompt_population_init_function` to mutate the child's parameter.
447-
- Repeat until the new population reaches `ga_population_size` (or `ga_offspring_size` offspring plus elites).
449+
- Repeat until the new population reaches `ga_population_size`.
448450
6. Repeat steps 2–5 for `ga_generations` generations.
449451

450452
All LLM calls and evaluations are executed asynchronously with a concurrency limit of `ga_parallel_evaluations`.

docs/source/resources/migration-guide.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,26 @@ To migrate:
9494
- `pip install nvidia-nat-profiler`
9595
- `pip install nvidia-nat-security`
9696

97+
#### Configuration Optimizer Package Extraction (Breaking)
98+
99+
Optimizer ownership now lives in the optional `nvidia-nat-config-optimizer` package.
100+
101+
This is a breaking change:
102+
- The `nat optimize` command is no longer owned by core and is only available when `nvidia-nat-config-optimizer` is installed.
103+
- Optimizer implementation modules moved from core paths into `nat.plugins.config_optimizer.*`.
104+
- The `nvidia-nat[optimizer]` extra has been renamed to `nvidia-nat[config-optimizer]`.
105+
106+
To migrate:
107+
- Install config optimizer support when needed:
108+
- `pip install "nvidia-nat[config-optimizer]"`
109+
- `pip install nvidia-nat-config-optimizer`
110+
- Update optimizer imports:
111+
- `nat.parameter_optimization.prompt_optimizer` => `nat.plugins.config_optimizer.prompts.ga_prompt_optimizer`
112+
- `nat.parameter_optimization.parameter_optimizer` => `nat.plugins.config_optimizer.parameters.optimizer`
113+
- `nat.parameter_optimization.optimizer_runtime` => `nat.plugins.config_optimizer.optimizer_runtime`
114+
- Keep optimizer callbacks at their core path:
115+
- `nat.profiler.parameter_optimization.optimizer_callbacks`
116+
97117
### v1.5.0
98118

99119
#### Removing Old Aliases and Transitional Packages
@@ -126,15 +146,14 @@ To migrate:
126146
- `pip install "nvidia-nat[profiling]"`
127147
- `pip install "nvidia-nat-eval[profiling]"`
128148
- Treat these commands as eval-owned commands that require `nvidia-nat-eval`: `nat eval`, `nat red-team`, and `nat sizing`.
129-
- Keep using `nat optimize` from core, but note that it now requires `nvidia-nat-eval` at runtime for evaluation execution.
130149

131150
#### Import Path Changes
132151

133-
For users migrating existing integrations, the primary import change is:
134-
- `nat.eval.*` -> `nat.plugins.eval.*`
135-
- `nat.profiler.*` -> `nat.plugins.eval.profiler.*`
136-
- `nat.profiler.parameter_optimization.*` -> `nat.parameter_optimization.*`
137-
- `nat.eval.runtime_event_subscriber.pull_intermediate` -> `nat.builder.runtime_event_subscriber.pull_intermediate`
152+
For users migrating existing integrations, the primary import change is (old => new):
153+
154+
- `nat.eval.*` => `nat.plugins.eval.*`
155+
- `nat.profiler.*` => `nat.plugins.eval.profiler.*` (except `nat.profiler.parameter_optimization.*`, which remains in core)
156+
- `nat.eval.runtime_event_subscriber.pull_intermediate` => `nat.builder.runtime_event_subscriber.pull_intermediate`
138157

139158
For evaluation data models, prefer canonical core paths:
140159
- `nat.data_models.evaluator` for `EvalInput*` / `EvalOutput*`

0 commit comments

Comments
 (0)