nvidia-nat 1.3.0.dev2__py3-none-any.whl → 1.3.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. aiq/__init__.py +2 -2
  2. nat/agent/base.py +24 -15
  3. nat/agent/dual_node.py +9 -4
  4. nat/agent/prompt_optimizer/prompt.py +68 -0
  5. nat/agent/prompt_optimizer/register.py +149 -0
  6. nat/agent/react_agent/agent.py +79 -47
  7. nat/agent/react_agent/register.py +41 -21
  8. nat/agent/reasoning_agent/reasoning_agent.py +11 -9
  9. nat/agent/register.py +1 -1
  10. nat/agent/rewoo_agent/agent.py +326 -148
  11. nat/agent/rewoo_agent/prompt.py +19 -22
  12. nat/agent/rewoo_agent/register.py +46 -26
  13. nat/agent/tool_calling_agent/agent.py +84 -28
  14. nat/agent/tool_calling_agent/register.py +51 -28
  15. nat/authentication/api_key/api_key_auth_provider.py +2 -2
  16. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  17. nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
  18. nat/authentication/interfaces.py +5 -2
  19. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +40 -20
  20. nat/authentication/oauth2/oauth2_resource_server_config.py +124 -0
  21. nat/authentication/register.py +0 -1
  22. nat/builder/builder.py +56 -24
  23. nat/builder/component_utils.py +9 -5
  24. nat/builder/context.py +46 -11
  25. nat/builder/eval_builder.py +16 -11
  26. nat/builder/framework_enum.py +1 -0
  27. nat/builder/front_end.py +1 -1
  28. nat/builder/function.py +378 -8
  29. nat/builder/function_base.py +3 -3
  30. nat/builder/function_info.py +6 -8
  31. nat/builder/user_interaction_manager.py +2 -2
  32. nat/builder/workflow.py +13 -1
  33. nat/builder/workflow_builder.py +281 -76
  34. nat/cli/cli_utils/config_override.py +2 -2
  35. nat/cli/commands/evaluate.py +1 -1
  36. nat/cli/commands/info/info.py +16 -6
  37. nat/cli/commands/info/list_channels.py +1 -1
  38. nat/cli/commands/info/list_components.py +7 -8
  39. nat/cli/commands/mcp/__init__.py +14 -0
  40. nat/cli/commands/mcp/mcp.py +986 -0
  41. nat/cli/commands/object_store/__init__.py +14 -0
  42. nat/cli/commands/object_store/object_store.py +227 -0
  43. nat/cli/commands/optimize.py +90 -0
  44. nat/cli/commands/registry/publish.py +2 -2
  45. nat/cli/commands/registry/pull.py +2 -2
  46. nat/cli/commands/registry/remove.py +2 -2
  47. nat/cli/commands/registry/search.py +15 -17
  48. nat/cli/commands/start.py +16 -5
  49. nat/cli/commands/uninstall.py +1 -1
  50. nat/cli/commands/workflow/templates/config.yml.j2 +0 -1
  51. nat/cli/commands/workflow/templates/pyproject.toml.j2 +4 -1
  52. nat/cli/commands/workflow/templates/register.py.j2 +0 -1
  53. nat/cli/commands/workflow/workflow_commands.py +9 -13
  54. nat/cli/entrypoint.py +8 -10
  55. nat/cli/register_workflow.py +38 -4
  56. nat/cli/type_registry.py +75 -6
  57. nat/control_flow/__init__.py +0 -0
  58. nat/control_flow/register.py +20 -0
  59. nat/control_flow/router_agent/__init__.py +0 -0
  60. nat/control_flow/router_agent/agent.py +329 -0
  61. nat/control_flow/router_agent/prompt.py +48 -0
  62. nat/control_flow/router_agent/register.py +91 -0
  63. nat/control_flow/sequential_executor.py +166 -0
  64. nat/data_models/agent.py +34 -0
  65. nat/data_models/api_server.py +10 -10
  66. nat/data_models/authentication.py +23 -9
  67. nat/data_models/common.py +1 -1
  68. nat/data_models/component.py +2 -0
  69. nat/data_models/component_ref.py +11 -0
  70. nat/data_models/config.py +41 -17
  71. nat/data_models/dataset_handler.py +1 -1
  72. nat/data_models/discovery_metadata.py +4 -4
  73. nat/data_models/evaluate.py +4 -1
  74. nat/data_models/function.py +34 -0
  75. nat/data_models/function_dependencies.py +14 -6
  76. nat/data_models/gated_field_mixin.py +242 -0
  77. nat/data_models/intermediate_step.py +3 -3
  78. nat/data_models/optimizable.py +119 -0
  79. nat/data_models/optimizer.py +149 -0
  80. nat/data_models/swe_bench_model.py +1 -1
  81. nat/data_models/temperature_mixin.py +44 -0
  82. nat/data_models/thinking_mixin.py +86 -0
  83. nat/data_models/top_p_mixin.py +44 -0
  84. nat/embedder/nim_embedder.py +1 -1
  85. nat/embedder/openai_embedder.py +1 -1
  86. nat/embedder/register.py +0 -1
  87. nat/eval/config.py +3 -1
  88. nat/eval/dataset_handler/dataset_handler.py +71 -7
  89. nat/eval/evaluate.py +86 -31
  90. nat/eval/evaluator/base_evaluator.py +1 -1
  91. nat/eval/evaluator/evaluator_model.py +13 -0
  92. nat/eval/intermediate_step_adapter.py +1 -1
  93. nat/eval/rag_evaluator/evaluate.py +2 -2
  94. nat/eval/rag_evaluator/register.py +3 -3
  95. nat/eval/register.py +4 -1
  96. nat/eval/remote_workflow.py +3 -3
  97. nat/eval/runtime_evaluator/__init__.py +14 -0
  98. nat/eval/runtime_evaluator/evaluate.py +123 -0
  99. nat/eval/runtime_evaluator/register.py +100 -0
  100. nat/eval/swe_bench_evaluator/evaluate.py +6 -6
  101. nat/eval/trajectory_evaluator/evaluate.py +1 -1
  102. nat/eval/trajectory_evaluator/register.py +1 -1
  103. nat/eval/tunable_rag_evaluator/evaluate.py +4 -7
  104. nat/eval/utils/eval_trace_ctx.py +89 -0
  105. nat/eval/utils/weave_eval.py +18 -9
  106. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  107. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  108. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +3 -3
  109. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
  110. nat/experimental/test_time_compute/models/strategy_base.py +5 -4
  111. nat/experimental/test_time_compute/register.py +0 -1
  112. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -3
  113. nat/front_ends/console/authentication_flow_handler.py +82 -30
  114. nat/front_ends/console/console_front_end_plugin.py +8 -5
  115. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  116. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  117. nat/front_ends/fastapi/fastapi_front_end_config.py +36 -5
  118. nat/front_ends/fastapi/fastapi_front_end_controller.py +4 -4
  119. nat/front_ends/fastapi/fastapi_front_end_plugin.py +135 -4
  120. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +481 -281
  121. nat/front_ends/fastapi/job_store.py +518 -99
  122. nat/front_ends/fastapi/main.py +11 -19
  123. nat/front_ends/fastapi/message_handler.py +13 -14
  124. nat/front_ends/fastapi/message_validator.py +17 -19
  125. nat/front_ends/fastapi/response_helpers.py +4 -4
  126. nat/front_ends/fastapi/step_adaptor.py +2 -2
  127. nat/front_ends/fastapi/utils.py +57 -0
  128. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  129. nat/front_ends/mcp/mcp_front_end_config.py +10 -1
  130. nat/front_ends/mcp/mcp_front_end_plugin.py +45 -13
  131. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +116 -8
  132. nat/front_ends/mcp/tool_converter.py +44 -14
  133. nat/front_ends/register.py +0 -1
  134. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  135. nat/llm/aws_bedrock_llm.py +24 -12
  136. nat/llm/azure_openai_llm.py +13 -6
  137. nat/llm/litellm_llm.py +69 -0
  138. nat/llm/nim_llm.py +20 -8
  139. nat/llm/openai_llm.py +14 -6
  140. nat/llm/register.py +4 -1
  141. nat/llm/utils/env_config_value.py +2 -3
  142. nat/llm/utils/thinking.py +215 -0
  143. nat/meta/pypi.md +9 -9
  144. nat/object_store/register.py +0 -1
  145. nat/observability/exporter/base_exporter.py +3 -3
  146. nat/observability/exporter/file_exporter.py +1 -1
  147. nat/observability/exporter/processing_exporter.py +309 -81
  148. nat/observability/exporter/span_exporter.py +1 -1
  149. nat/observability/exporter_manager.py +7 -7
  150. nat/observability/mixin/file_mixin.py +7 -7
  151. nat/observability/mixin/redaction_config_mixin.py +42 -0
  152. nat/observability/mixin/tagging_config_mixin.py +62 -0
  153. nat/observability/mixin/type_introspection_mixin.py +420 -107
  154. nat/observability/processor/batching_processor.py +5 -7
  155. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  156. nat/observability/processor/processor.py +3 -0
  157. nat/observability/processor/processor_factory.py +70 -0
  158. nat/observability/processor/redaction/__init__.py +24 -0
  159. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  160. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  161. nat/observability/processor/redaction/redaction_processor.py +177 -0
  162. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  163. nat/observability/processor/span_tagging_processor.py +68 -0
  164. nat/observability/register.py +6 -4
  165. nat/profiler/calc/calc_runner.py +3 -4
  166. nat/profiler/callbacks/agno_callback_handler.py +1 -1
  167. nat/profiler/callbacks/langchain_callback_handler.py +6 -6
  168. nat/profiler/callbacks/llama_index_callback_handler.py +3 -3
  169. nat/profiler/callbacks/semantic_kernel_callback_handler.py +3 -3
  170. nat/profiler/data_frame_row.py +1 -1
  171. nat/profiler/decorators/framework_wrapper.py +62 -13
  172. nat/profiler/decorators/function_tracking.py +160 -3
  173. nat/profiler/forecasting/models/forecasting_base_model.py +3 -1
  174. nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +1 -1
  175. nat/profiler/inference_optimization/data_models.py +3 -3
  176. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +7 -8
  177. nat/profiler/inference_optimization/token_uniqueness.py +1 -1
  178. nat/profiler/parameter_optimization/__init__.py +0 -0
  179. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  180. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  181. nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
  182. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  183. nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
  184. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  185. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  186. nat/profiler/profile_runner.py +14 -9
  187. nat/profiler/utils.py +4 -2
  188. nat/registry_handlers/local/local_handler.py +2 -2
  189. nat/registry_handlers/package_utils.py +1 -2
  190. nat/registry_handlers/pypi/pypi_handler.py +23 -26
  191. nat/registry_handlers/register.py +3 -4
  192. nat/registry_handlers/rest/rest_handler.py +12 -13
  193. nat/retriever/milvus/retriever.py +2 -2
  194. nat/retriever/nemo_retriever/retriever.py +1 -1
  195. nat/retriever/register.py +0 -1
  196. nat/runtime/loader.py +2 -2
  197. nat/runtime/runner.py +3 -2
  198. nat/runtime/session.py +43 -8
  199. nat/settings/global_settings.py +16 -5
  200. nat/tool/chat_completion.py +5 -2
  201. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +3 -3
  202. nat/tool/datetime_tools.py +49 -9
  203. nat/tool/document_search.py +2 -2
  204. nat/tool/github_tools.py +450 -0
  205. nat/tool/nvidia_rag.py +1 -1
  206. nat/tool/register.py +2 -9
  207. nat/tool/retriever.py +3 -2
  208. nat/utils/callable_utils.py +70 -0
  209. nat/utils/data_models/schema_validator.py +3 -3
  210. nat/utils/exception_handlers/automatic_retries.py +104 -51
  211. nat/utils/exception_handlers/schemas.py +1 -1
  212. nat/utils/io/yaml_tools.py +2 -2
  213. nat/utils/log_levels.py +25 -0
  214. nat/utils/reactive/base/observable_base.py +2 -2
  215. nat/utils/reactive/base/observer_base.py +1 -1
  216. nat/utils/reactive/observable.py +2 -2
  217. nat/utils/reactive/observer.py +4 -4
  218. nat/utils/reactive/subscription.py +1 -1
  219. nat/utils/settings/global_settings.py +6 -8
  220. nat/utils/type_converter.py +4 -3
  221. nat/utils/type_utils.py +9 -5
  222. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/METADATA +42 -16
  223. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/RECORD +230 -189
  224. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/entry_points.txt +1 -0
  225. nat/cli/commands/info/list_mcp.py +0 -304
  226. nat/tool/github_tools/create_github_commit.py +0 -133
  227. nat/tool/github_tools/create_github_issue.py +0 -87
  228. nat/tool/github_tools/create_github_pr.py +0 -106
  229. nat/tool/github_tools/get_github_file.py +0 -106
  230. nat/tool/github_tools/get_github_issue.py +0 -166
  231. nat/tool/github_tools/get_github_pr.py +0 -256
  232. nat/tool/github_tools/update_github_issue.py +0 -100
  233. nat/tool/mcp/exceptions.py +0 -142
  234. nat/tool/mcp/mcp_client.py +0 -255
  235. nat/tool/mcp/mcp_tool.py +0 -96
  236. nat/utils/exception_handlers/mcp.py +0 -211
  237. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  238. /nat/{tool/mcp → authentication/credential_validator}/__init__.py +0 -0
  239. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/WHEEL +0 -0
  240. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  241. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/licenses/LICENSE.md +0 -0
  242. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
@@ -0,0 +1,227 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import asyncio
17
+ import importlib
18
+ import logging
19
+ import mimetypes
20
+ import time
21
+ from pathlib import Path
22
+
23
+ import click
24
+
25
+ from nat.builder.workflow_builder import WorkflowBuilder
26
+ from nat.data_models.object_store import ObjectStoreBaseConfig
27
+ from nat.object_store.interfaces import ObjectStore
28
+ from nat.object_store.models import ObjectStoreItem
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ STORE_CONFIGS = {
33
+ "s3": {
34
+ "module": "nat.plugins.s3.object_store", "config_class": "S3ObjectStoreClientConfig"
35
+ },
36
+ "mysql": {
37
+ "module": "nat.plugins.mysql.object_store", "config_class": "MySQLObjectStoreClientConfig"
38
+ },
39
+ "redis": {
40
+ "module": "nat.plugins.redis.object_store", "config_class": "RedisObjectStoreClientConfig"
41
+ }
42
+ }
43
+
44
+
45
+ def get_object_store_config(**kwargs) -> ObjectStoreBaseConfig:
46
+ """Process common object store arguments and return the config class"""
47
+ store_type = kwargs.pop("store_type")
48
+ config = STORE_CONFIGS[store_type]
49
+ module = importlib.import_module(config["module"])
50
+ config_class = getattr(module, config["config_class"])
51
+ return config_class(**kwargs)
52
+
53
+
54
+ async def upload_file(object_store: ObjectStore, file_path: Path, key: str):
55
+ """
56
+ Upload a single file to object store.
57
+
58
+ Args:
59
+ object_store: The object store instance to use.
60
+ file_path: The path to the file to upload.
61
+ key: The key to upload the file to.
62
+ """
63
+ try:
64
+ data = await asyncio.to_thread(file_path.read_bytes)
65
+
66
+ item = ObjectStoreItem(data=data,
67
+ content_type=mimetypes.guess_type(str(file_path))[0],
68
+ metadata={
69
+ "original_filename": file_path.name,
70
+ "file_size": str(len(data)),
71
+ "file_extension": file_path.suffix,
72
+ "upload_timestamp": str(int(time.time()))
73
+ })
74
+
75
+ # Upload using upsert to allow overwriting
76
+ await object_store.upsert_object(key, item)
77
+ click.echo(f"✅ Uploaded: {file_path.name} -> {key}")
78
+
79
+ except Exception as e:
80
+ raise RuntimeError(f"Failed to upload {file_path.name}:\n{e}") from e
81
+
82
+
83
+ def object_store_command_decorator(async_func):
84
+ """
85
+ Decorator that handles the common object store command pattern.
86
+
87
+ The decorated function should take (store: ObjectStore, kwargs) as parameters
88
+ and return an exit code (0 for success).
89
+ """
90
+
91
+ @click.pass_context
92
+ def wrapper(ctx: click.Context, **kwargs):
93
+ config = ctx.obj["store_config"]
94
+
95
+ async def work():
96
+ async with WorkflowBuilder() as builder:
97
+ await builder.add_object_store(name="store", config=config)
98
+ store = await builder.get_object_store_client("store")
99
+ return await async_func(store, **kwargs)
100
+
101
+ try:
102
+ exit_code = asyncio.run(work())
103
+ except Exception as e:
104
+ raise click.ClickException(f"Command failed: {e}") from e
105
+ if exit_code != 0:
106
+ raise click.ClickException(f"Command failed with exit code {exit_code}")
107
+ return exit_code
108
+
109
+ return wrapper
110
+
111
+
112
+ @click.command(name="upload", help="Upload a directory to an object store.")
113
+ @click.argument("local_dir",
114
+ type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
115
+ required=True)
116
+ @click.help_option("--help", "-h")
117
+ @object_store_command_decorator
118
+ async def upload_command(store: ObjectStore, local_dir: Path, **_kwargs):
119
+ """
120
+ Upload a directory to an object store.
121
+
122
+ Args:
123
+ local_dir: The local directory to upload.
124
+ store: The object store to use.
125
+ _kwargs: Additional keyword arguments.
126
+ """
127
+ try:
128
+ click.echo(f"📁 Processing directory: {local_dir}")
129
+ file_count = 0
130
+
131
+ # Process each file recursively
132
+ for file_path in local_dir.rglob("*"):
133
+ if file_path.is_file():
134
+ key = file_path.relative_to(local_dir).as_posix()
135
+ await upload_file(store, file_path, key)
136
+ file_count += 1
137
+
138
+ click.echo(f"✅ Directory uploaded successfully! {file_count} files uploaded.")
139
+ return 0
140
+
141
+ except Exception as e:
142
+ raise click.ClickException(f"❌ Failed to upload directory {local_dir}:\n {e}") from e
143
+
144
+
145
+ @click.command(name="delete", help="Delete files from an object store.")
146
+ @click.argument("keys", type=str, required=True, nargs=-1)
147
+ @click.help_option("--help", "-h")
148
+ @object_store_command_decorator
149
+ async def delete_command(store: ObjectStore, keys: list[str], **_kwargs):
150
+ """
151
+ Delete files from an object store.
152
+
153
+ Args:
154
+ store: The object store to use.
155
+ keys: The keys to delete.
156
+ _kwargs: Additional keyword arguments.
157
+ """
158
+ deleted_count = 0
159
+ failed_count = 0
160
+ for key in keys:
161
+ try:
162
+ await store.delete_object(key)
163
+ click.echo(f"✅ Deleted: {key}")
164
+ deleted_count += 1
165
+ except Exception as e:
166
+ click.echo(f"❌ Failed to delete {key}: {e}")
167
+ failed_count += 1
168
+
169
+ click.echo(f"✅ Deletion completed! {deleted_count} keys deleted. {failed_count} keys failed to delete.")
170
+ return 0 if failed_count == 0 else 1
171
+
172
+
173
+ @click.group(name="object-store", invoke_without_command=False, help="Manage object store operations.")
174
+ def object_store_command(**_kwargs):
175
+ """Manage object store operations including uploading files and directories."""
176
+ pass
177
+
178
+
179
+ def register_object_store_commands():
180
+
181
+ @click.group(name="s3", invoke_without_command=False, help="S3 object store operations.")
182
+ @click.argument("bucket_name", type=str, required=True)
183
+ @click.option("--endpoint-url", type=str, help="S3 endpoint URL")
184
+ @click.option("--access-key", type=str, help="S3 access key")
185
+ @click.option("--secret-key", type=str, help="S3 secret key")
186
+ @click.option("--region", type=str, help="S3 region")
187
+ @click.pass_context
188
+ def s3(ctx: click.Context, **kwargs):
189
+ ctx.ensure_object(dict)
190
+ ctx.obj["store_config"] = get_object_store_config(store_type="s3", **kwargs)
191
+
192
+ @click.group(name="mysql", invoke_without_command=False, help="MySQL object store operations.")
193
+ @click.argument("bucket_name", type=str, required=True)
194
+ @click.option("--host", type=str, help="MySQL host")
195
+ @click.option("--port", type=int, help="MySQL port")
196
+ @click.option("--db", type=str, help="MySQL database name")
197
+ @click.option("--username", type=str, help="MySQL username")
198
+ @click.option("--password", type=str, help="MySQL password")
199
+ @click.pass_context
200
+ def mysql(ctx: click.Context, **kwargs):
201
+ ctx.ensure_object(dict)
202
+ ctx.obj["store_config"] = get_object_store_config(store_type="mysql", **kwargs)
203
+
204
+ @click.group(name="redis", invoke_without_command=False, help="Redis object store operations.")
205
+ @click.argument("bucket_name", type=str, required=True)
206
+ @click.option("--host", type=str, help="Redis host")
207
+ @click.option("--port", type=int, help="Redis port")
208
+ @click.option("--db", type=int, help="Redis db")
209
+ @click.pass_context
210
+ def redis(ctx: click.Context, **kwargs):
211
+ ctx.ensure_object(dict)
212
+ ctx.obj["store_config"] = get_object_store_config(store_type="redis", **kwargs)
213
+
214
+ commands = {"s3": s3, "mysql": mysql, "redis": redis}
215
+
216
+ for store_type, config in STORE_CONFIGS.items():
217
+ try:
218
+ importlib.import_module(config["module"])
219
+ command = commands[store_type]
220
+ object_store_command.add_command(command, name=store_type)
221
+ command.add_command(upload_command, name="upload")
222
+ command.add_command(delete_command, name="delete")
223
+ except ImportError:
224
+ pass
225
+
226
+
227
+ register_object_store_commands()
@@ -0,0 +1,90 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ import asyncio
17
+ import logging
18
+ from pathlib import Path
19
+
20
+ import click
21
+
22
+ from nat.data_models.optimizer import OptimizerRunConfig
23
+ from nat.profiler.parameter_optimization.optimizer_runtime import optimize_config
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @click.group(name=__name__, invoke_without_command=True, help="Optimize a workflow with the specified dataset.")
29
+ @click.option(
30
+ "--config_file",
31
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
32
+ required=True,
33
+ help="A JSON/YAML file that sets the parameters for the workflow and evaluation.",
34
+ )
35
+ @click.option(
36
+ "--dataset",
37
+ type=click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path),
38
+ required=False,
39
+ help="A json file with questions and ground truth answers. This will override the dataset path in the config file.",
40
+ )
41
+ @click.option(
42
+ "--result_json_path",
43
+ type=str,
44
+ default="$",
45
+ help=("A JSON path to extract the result from the workflow. Use this when the workflow returns "
46
+ "multiple objects or a dictionary. For example, '$.output' will extract the 'output' field "
47
+ "from the result."),
48
+ )
49
+ @click.option(
50
+ "--endpoint",
51
+ type=str,
52
+ default=None,
53
+ help="Use endpoint for running the workflow. Example: http://localhost:8000/generate",
54
+ )
55
+ @click.option(
56
+ "--endpoint_timeout",
57
+ type=int,
58
+ default=300,
59
+ help="HTTP response timeout in seconds. Only relevant if endpoint is specified.",
60
+ )
61
+ @click.pass_context
62
+ def optimizer_command(ctx, **kwargs) -> None:
63
+ """ Optimize workflow with the specified dataset"""
64
+ pass
65
+
66
+
67
+ async def run_optimizer(config: OptimizerRunConfig):
68
+ await optimize_config(config)
69
+
70
+
71
+ @optimizer_command.result_callback(replace=True)
72
+ def run_optimizer_callback(
73
+ processors, # pylint: disable=unused-argument
74
+ *,
75
+ config_file: Path,
76
+ dataset: Path,
77
+ result_json_path: str,
78
+ endpoint: str,
79
+ endpoint_timeout: int,
80
+ ):
81
+ """Run the optimizer with the provided config file and dataset."""
82
+ config = OptimizerRunConfig(
83
+ config_file=config_file,
84
+ dataset=dataset,
85
+ result_json_path=result_json_path,
86
+ endpoint=endpoint,
87
+ endpoint_timeout=endpoint_timeout,
88
+ )
89
+
90
+ asyncio.run(run_optimizer(config))
@@ -40,7 +40,7 @@ async def publish_artifact(registry_handler_config: RegistryHandlerBaseConfig, p
40
40
  try:
41
41
  artifact = build_artifact(package_root=package_root)
42
42
  except Exception as e:
43
- logger.exception("Error building artifact: %s", e, exc_info=True)
43
+ logger.exception("Error building artifact: %s", e)
44
44
  return
45
45
  await stack.enter_async_context(registry_handler.publish(artifact=artifact))
46
46
 
@@ -82,7 +82,7 @@ def publish(channel: str, config_file: str, package_root: str) -> None:
82
82
  logger.error("Publish channel '%s' has not been configured.", channel)
83
83
  return
84
84
  except Exception as e:
85
- logger.exception("Error loading user settings: %s", e, exc_info=True)
85
+ logger.exception("Error loading user settings: %s", e)
86
86
  return
87
87
 
88
88
  asyncio.run(publish_artifact(registry_handler_config=publish_channel_config, package_root=package_root))
@@ -66,7 +66,7 @@ async def pull_artifact(registry_handler_config: RegistryHandlerBaseConfig, pack
66
66
  validated_packages = PullRequestPackages(packages=package_list)
67
67
 
68
68
  except Exception as e:
69
- logger.exception("Error processing package names: %s", e, exc_info=True)
69
+ logger.exception("Error processing package names: %s", e)
70
70
  return
71
71
 
72
72
  await stack.enter_async_context(registry_handler.pull(packages=validated_packages))
@@ -112,7 +112,7 @@ def pull(channel: str, config_file: str, packages: str) -> None:
112
112
  logger.error("Pull channel '%s' has not been configured.", channel)
113
113
  return
114
114
  except Exception as e:
115
- logger.exception("Error loading user settings: %s", e, exc_info=True)
115
+ logger.exception("Error loading user settings: %s", e)
116
116
  return
117
117
 
118
118
  asyncio.run(pull_artifact(pull_channel_config, packages))
@@ -41,7 +41,7 @@ async def remove_artifact(registry_handler_config: RegistryHandlerBaseConfig, pa
41
41
  try:
42
42
  package_name_list = PackageNameVersionList(**{"packages": packages})
43
43
  except Exception as e:
44
- logger.exception("Invalid package format: '%s'", e, exc_info=True)
44
+ logger.exception("Invalid package format: '%s'", e)
45
45
 
46
46
  await stack.enter_async_context(registry_handler.remove(packages=package_name_list))
47
47
 
@@ -102,7 +102,7 @@ def remove(channel: str, config_file: str, packages: str) -> None:
102
102
  logger.error("Remove channel '%s' has not been configured.", channel)
103
103
  return
104
104
  except Exception as e:
105
- logger.exception("Error loading user settings: %s", e, exc_info=True)
105
+ logger.exception("Error loading user settings: %s", e)
106
106
  return
107
107
 
108
108
  asyncio.run(remove_artifact(registry_handler_config=remove_channel_config, packages=packages_versions))
@@ -29,14 +29,13 @@ from nat.utils.data_models.schema_validator import validate_yaml
29
29
  logger = logging.getLogger(__name__)
30
30
 
31
31
 
32
- async def search_artifacts( # pylint: disable=R0917
33
- registry_handler_config: RegistryHandlerBaseConfig,
34
- query: str,
35
- search_fields: list[SearchFields],
36
- visualize: bool,
37
- component_types: list[ComponentEnum],
38
- save_path: str | None = None,
39
- n_results: int = 10) -> None:
32
+ async def search_artifacts(registry_handler_config: RegistryHandlerBaseConfig,
33
+ query: str,
34
+ search_fields: list[SearchFields],
35
+ visualize: bool,
36
+ component_types: list[ComponentEnum],
37
+ save_path: str | None = None,
38
+ n_results: int = 10) -> None:
40
39
 
41
40
  from nat.cli.type_registry import GlobalTypeRegistry
42
41
  from nat.registry_handlers.schemas.search import SearchQuery
@@ -116,14 +115,13 @@ async def search_artifacts( # pylint: disable=R0917
116
115
  required=False,
117
116
  help=("The component types to include in search."),
118
117
  )
119
- def search( # pylint: disable=R0917
120
- config_file: str,
121
- channel: str,
122
- fields: list[str],
123
- query: str,
124
- component_types: list[ComponentEnum],
125
- n_results: int,
126
- output_path: str) -> None:
118
+ def search(config_file: str,
119
+ channel: str,
120
+ fields: list[str],
121
+ query: str,
122
+ component_types: list[ComponentEnum],
123
+ n_results: int,
124
+ output_path: str) -> None:
127
125
  """
128
126
  Search for NAT artifacts with the specified configuration.
129
127
  """
@@ -142,7 +140,7 @@ def search( # pylint: disable=R0917
142
140
  logger.error("Search channel '%s' has not been configured.", channel)
143
141
  return
144
142
  except Exception as e:
145
- logger.exception("Error loading user settings: %s", e, exc_info=True)
143
+ logger.exception("Error loading user settings: %s", e)
146
144
  return
147
145
 
148
146
  asyncio.run(
nat/cli/commands/start.py CHANGED
@@ -35,7 +35,6 @@ logger = logging.getLogger(__name__)
35
35
 
36
36
  class StartCommandGroup(click.Group):
37
37
 
38
- # pylint: disable=too-many-positional-arguments
39
38
  def __init__(
40
39
  self,
41
40
  name: str | None = None,
@@ -103,12 +102,24 @@ class StartCommandGroup(click.Group):
103
102
  raise ValueError(f"Invalid field '{name}'.Unions are only supported for optional parameters.")
104
103
 
105
104
  # Handle the types
106
- if (issubclass(decomposed_type.root, Path)):
105
+ # Literal[...] -> map to click.Choice([...])
106
+ if (decomposed_type.origin is typing.Literal):
107
+ # typing.get_args returns the literal values; ensure they are strings for Click
108
+ literal_values = [str(v) for v in decomposed_type.args]
109
+ param_type = click.Choice(literal_values)
110
+
111
+ elif (issubclass(decomposed_type.root, Path)):
107
112
  param_type = click.Path(exists=True, file_okay=True, dir_okay=False, path_type=Path)
108
113
 
109
- elif (issubclass(decomposed_type.root, (list, tuple, set))):
114
+ elif (issubclass(decomposed_type.root, list | tuple | set)):
110
115
  if (len(decomposed_type.args) == 1):
111
- param_type = decomposed_type.args[0]
116
+ inner = DecomposedType(decomposed_type.args[0])
117
+ # Support containers of Literal values -> multiple Choice
118
+ if (inner.origin is typing.Literal):
119
+ literal_values = [str(v) for v in inner.args]
120
+ param_type = click.Choice(literal_values)
121
+ else:
122
+ param_type = inner.root
112
123
  else:
113
124
  param_type = None
114
125
 
@@ -225,7 +236,7 @@ class StartCommandGroup(click.Group):
225
236
  return asyncio.run(run_plugin())
226
237
 
227
238
  except Exception as e:
228
- logger.error("Failed to initialize workflow", exc_info=True)
239
+ logger.error("Failed to initialize workflow")
229
240
  raise click.ClickException(str(e)) from e
230
241
 
231
242
  def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
@@ -44,7 +44,7 @@ async def uninstall_packages(packages: list[dict[str, str]]) -> None:
44
44
  try:
45
45
  package_name_list = PackageNameVersionList(**{"packages": packages})
46
46
  except Exception as e:
47
- logger.exception("Error validating package format: %s", e, exc_info=True)
47
+ logger.exception("Error validating package format: %s", e)
48
48
  return
49
49
 
50
50
  async with AsyncExitStack() as stack:
@@ -1,5 +1,4 @@
1
1
  general:
2
- use_uvloop: true
3
2
  logging:
4
3
  console:
5
4
  _type: console
@@ -3,6 +3,9 @@ build-backend = "setuptools.build_meta"
3
3
  {% if editable %}requires = ["setuptools >= 64", "setuptools-scm>=8"]
4
4
 
5
5
  [tool.setuptools_scm]
6
+ # NAT uses the --first-parent flag to avoid tags from previous releases which have been merged into the develop branch
7
+ # from causing an unexpected version change. This can be safely removed if developing outside of the NAT repository.
8
+ git_describe_command = "git describe --long --first-parent"
6
9
  root = "{{ rel_path_to_repo_root}}"{% else %}requires = ["setuptools >= 64"]{% endif %}
7
10
 
8
11
  [project]
@@ -11,7 +14,7 @@ name = "{{ package_name }}"
11
14
  dependencies = [
12
15
  "{{ nat_dependency }}",
13
16
  ]
14
- requires-python = ">=3.11,<3.13"
17
+ requires-python = ">=3.11,<3.14"
15
18
  description = "Custom NeMo Agent Toolkit Workflow"
16
19
  classifiers = ["Programming Language :: Python"]
17
20
 
@@ -1,4 +1,3 @@
1
- # pylint: disable=unused-import
2
1
  # flake8: noqa
3
2
 
4
3
  # Import any tools which need to be automatically registered here
@@ -37,7 +37,7 @@ def _get_nat_dependency(versioned: bool = True) -> str:
37
37
  Returns:
38
38
  str: The dependency string to use in pyproject.toml
39
39
  """
40
- # Assume the default dependency is langchain
40
+ # Assume the default dependency is LangChain/LangGraph
41
41
  dependency = "nvidia-nat[langchain]"
42
42
 
43
43
  if not versioned:
@@ -97,7 +97,7 @@ def find_package_root(package_name: str) -> Path | None:
97
97
  try:
98
98
  info = json.loads(direct_url)
99
99
  except json.JSONDecodeError:
100
- logger.error("Malformed direct_url.json for package: %s", package_name)
100
+ logger.exception("Malformed direct_url.json for package: %s", package_name)
101
101
  return None
102
102
 
103
103
  if not info.get("dir_info", {}).get("editable"):
@@ -161,7 +161,6 @@ def get_workflow_path_from_name(workflow_name: str):
161
161
  default="NAT function template. Please update the description.",
162
162
  help="""A description of the component being created. Will be used to populate the docstring and will describe the
163
163
  component when inspecting installed components using 'nat info component'""")
164
- # pylint: disable=missing-param-doc
165
164
  def create_command(workflow_name: str, install: bool, workflow_dir: str, description: str):
166
165
  """
167
166
  Create a new NAT workflow using templates.
@@ -172,6 +171,9 @@ def create_command(workflow_name: str, install: bool, workflow_dir: str, descrip
172
171
  workflow_dir (str): The directory to create the workflow package.
173
172
  description (str): Description to pre-popluate the workflow docstring.
174
173
  """
174
+ # Fail fast with Click's standard exit code (2) for bad params.
175
+ if not workflow_name or not workflow_name.strip():
176
+ raise click.BadParameter("Workflow name cannot be empty.") # noqa: TRY003
175
177
  try:
176
178
  # Get the repository root
177
179
  try:
@@ -218,15 +220,13 @@ def create_command(workflow_name: str, install: bool, workflow_dir: str, descrip
218
220
  else:
219
221
  install_cmd = ['pip', 'install', '-e', str(new_workflow_dir)]
220
222
 
221
- config_source = configs_dir / 'config.yml'
222
-
223
223
  # List of templates and their destinations
224
224
  files_to_render = {
225
225
  'pyproject.toml.j2': new_workflow_dir / 'pyproject.toml',
226
226
  'register.py.j2': base_dir / 'register.py',
227
227
  'workflow.py.j2': base_dir / f'{workflow_name}_function.py',
228
228
  '__init__.py.j2': base_dir / '__init__.py',
229
- 'config.yml.j2': config_source,
229
+ 'config.yml.j2': configs_dir / 'config.yml',
230
230
  }
231
231
 
232
232
  # Render templates
@@ -247,10 +247,6 @@ def create_command(workflow_name: str, install: bool, workflow_dir: str, descrip
247
247
  with open(output_path, 'w', encoding="utf-8") as f:
248
248
  f.write(content)
249
249
 
250
- # Create symlink for config.yml
251
- config_link = new_workflow_dir / 'configs' / 'config.yml'
252
- os.symlink(config_source, config_link)
253
-
254
250
  # Create symlinks for config and data directories
255
251
  config_dir_source = configs_dir
256
252
  config_dir_link = new_workflow_dir / 'configs'
@@ -272,7 +268,7 @@ def create_command(workflow_name: str, install: bool, workflow_dir: str, descrip
272
268
 
273
269
  click.echo(f"Workflow '{workflow_name}' created successfully in '{new_workflow_dir}'.")
274
270
  except Exception as e:
275
- logger.exception("An error occurred while creating the workflow: %s", e, exc_info=True)
271
+ logger.exception("An error occurred while creating the workflow: %s", e)
276
272
  click.echo(f"An error occurred while creating the workflow: {e}")
277
273
 
278
274
 
@@ -308,7 +304,7 @@ def reinstall_command(workflow_name):
308
304
 
309
305
  click.echo(f"Workflow '{workflow_name}' reinstalled successfully.")
310
306
  except Exception as e:
311
- logger.exception("An error occurred while reinstalling the workflow: %s", e, exc_info=True)
307
+ logger.exception("An error occurred while reinstalling the workflow: %s", e)
312
308
  click.echo(f"An error occurred while reinstalling the workflow: {e}")
313
309
 
314
310
 
@@ -355,7 +351,7 @@ def delete_command(workflow_name: str):
355
351
 
356
352
  click.echo(f"Workflow '{workflow_name}' deleted successfully.")
357
353
  except Exception as e:
358
- logger.exception("An error occurred while deleting the workflow: %s", e, exc_info=True)
354
+ logger.exception("An error occurred while deleting the workflow: %s", e)
359
355
  click.echo(f"An error occurred while deleting the workflow: {e}")
360
356
 
361
357
 
nat/cli/entrypoint.py CHANGED
@@ -30,9 +30,14 @@ import time
30
30
  import click
31
31
  import nest_asyncio
32
32
 
33
+ from nat.utils.log_levels import LOG_LEVELS
34
+
33
35
  from .commands.configure.configure import configure_command
34
36
  from .commands.evaluate import eval_command
35
37
  from .commands.info.info import info_command
38
+ from .commands.mcp.mcp import mcp_command
39
+ from .commands.object_store.object_store import object_store_command
40
+ from .commands.optimize import optimizer_command
36
41
  from .commands.registry.registry import registry_command
37
42
  from .commands.sizing.sizing import sizing
38
43
  from .commands.start import start_command
@@ -43,15 +48,6 @@ from .commands.workflow.workflow import workflow_command
43
48
  # Apply at the beginning of the file to avoid issues with asyncio
44
49
  nest_asyncio.apply()
45
50
 
46
- # Define log level choices
47
- LOG_LEVELS = {
48
- 'DEBUG': logging.DEBUG,
49
- 'INFO': logging.INFO,
50
- 'WARNING': logging.WARNING,
51
- 'ERROR': logging.ERROR,
52
- 'CRITICAL': logging.CRITICAL
53
- }
54
-
55
51
 
56
52
  def setup_logging(log_level: str):
57
53
  """Configure logging with the specified level"""
@@ -107,11 +103,13 @@ cli.add_command(uninstall_command, name="uninstall")
107
103
  cli.add_command(validate_command, name="validate")
108
104
  cli.add_command(workflow_command, name="workflow")
109
105
  cli.add_command(sizing, name="sizing")
106
+ cli.add_command(optimizer_command, name="optimize")
107
+ cli.add_command(object_store_command, name="object-store")
108
+ cli.add_command(mcp_command, name="mcp")
110
109
 
111
110
  # Aliases
112
111
  cli.add_command(start_command.get_command(None, "console"), name="run") # type: ignore
113
112
  cli.add_command(start_command.get_command(None, "fastapi"), name="serve") # type: ignore
114
- cli.add_command(start_command.get_command(None, "mcp"), name="mcp")
115
113
 
116
114
 
117
115
  @cli.result_callback()