nvidia-nat 1.3.0.dev2__py3-none-any.whl → 1.3.0rc2__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 (250) 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 +50 -22
  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 +54 -27
  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 +69 -36
  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 +68 -17
  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 +14 -13
  51. nat/cli/commands/workflow/templates/pyproject.toml.j2 +4 -1
  52. nat/cli/commands/workflow/templates/register.py.j2 +2 -3
  53. nat/cli/commands/workflow/templates/workflow.py.j2 +35 -21
  54. nat/cli/commands/workflow/workflow_commands.py +62 -22
  55. nat/cli/entrypoint.py +8 -10
  56. nat/cli/main.py +3 -0
  57. nat/cli/register_workflow.py +38 -4
  58. nat/cli/type_registry.py +75 -6
  59. nat/control_flow/__init__.py +0 -0
  60. nat/control_flow/register.py +20 -0
  61. nat/control_flow/router_agent/__init__.py +0 -0
  62. nat/control_flow/router_agent/agent.py +329 -0
  63. nat/control_flow/router_agent/prompt.py +48 -0
  64. nat/control_flow/router_agent/register.py +91 -0
  65. nat/control_flow/sequential_executor.py +166 -0
  66. nat/data_models/agent.py +34 -0
  67. nat/data_models/api_server.py +74 -66
  68. nat/data_models/authentication.py +23 -9
  69. nat/data_models/common.py +1 -1
  70. nat/data_models/component.py +2 -0
  71. nat/data_models/component_ref.py +11 -0
  72. nat/data_models/config.py +41 -17
  73. nat/data_models/dataset_handler.py +1 -1
  74. nat/data_models/discovery_metadata.py +4 -4
  75. nat/data_models/evaluate.py +4 -1
  76. nat/data_models/function.py +34 -0
  77. nat/data_models/function_dependencies.py +14 -6
  78. nat/data_models/gated_field_mixin.py +242 -0
  79. nat/data_models/intermediate_step.py +3 -3
  80. nat/data_models/optimizable.py +119 -0
  81. nat/data_models/optimizer.py +149 -0
  82. nat/data_models/span.py +41 -3
  83. nat/data_models/swe_bench_model.py +1 -1
  84. nat/data_models/temperature_mixin.py +44 -0
  85. nat/data_models/thinking_mixin.py +86 -0
  86. nat/data_models/top_p_mixin.py +44 -0
  87. nat/embedder/nim_embedder.py +1 -1
  88. nat/embedder/openai_embedder.py +1 -1
  89. nat/embedder/register.py +0 -1
  90. nat/eval/config.py +3 -1
  91. nat/eval/dataset_handler/dataset_handler.py +71 -7
  92. nat/eval/evaluate.py +86 -31
  93. nat/eval/evaluator/base_evaluator.py +1 -1
  94. nat/eval/evaluator/evaluator_model.py +13 -0
  95. nat/eval/intermediate_step_adapter.py +1 -1
  96. nat/eval/rag_evaluator/evaluate.py +2 -2
  97. nat/eval/rag_evaluator/register.py +3 -3
  98. nat/eval/register.py +4 -1
  99. nat/eval/remote_workflow.py +3 -3
  100. nat/eval/runtime_evaluator/__init__.py +14 -0
  101. nat/eval/runtime_evaluator/evaluate.py +123 -0
  102. nat/eval/runtime_evaluator/register.py +100 -0
  103. nat/eval/swe_bench_evaluator/evaluate.py +6 -6
  104. nat/eval/trajectory_evaluator/evaluate.py +1 -1
  105. nat/eval/trajectory_evaluator/register.py +1 -1
  106. nat/eval/tunable_rag_evaluator/evaluate.py +4 -7
  107. nat/eval/utils/eval_trace_ctx.py +89 -0
  108. nat/eval/utils/weave_eval.py +18 -9
  109. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  110. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  111. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +3 -3
  112. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
  113. nat/experimental/test_time_compute/models/strategy_base.py +5 -4
  114. nat/experimental/test_time_compute/register.py +0 -1
  115. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -3
  116. nat/front_ends/console/authentication_flow_handler.py +82 -30
  117. nat/front_ends/console/console_front_end_plugin.py +8 -5
  118. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  119. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  120. nat/front_ends/fastapi/fastapi_front_end_config.py +36 -5
  121. nat/front_ends/fastapi/fastapi_front_end_controller.py +4 -4
  122. nat/front_ends/fastapi/fastapi_front_end_plugin.py +135 -4
  123. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +452 -282
  124. nat/front_ends/fastapi/job_store.py +518 -99
  125. nat/front_ends/fastapi/main.py +11 -19
  126. nat/front_ends/fastapi/message_handler.py +13 -14
  127. nat/front_ends/fastapi/message_validator.py +19 -19
  128. nat/front_ends/fastapi/response_helpers.py +4 -4
  129. nat/front_ends/fastapi/step_adaptor.py +2 -2
  130. nat/front_ends/fastapi/utils.py +57 -0
  131. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  132. nat/front_ends/mcp/mcp_front_end_config.py +10 -1
  133. nat/front_ends/mcp/mcp_front_end_plugin.py +45 -13
  134. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +116 -8
  135. nat/front_ends/mcp/tool_converter.py +44 -14
  136. nat/front_ends/register.py +0 -1
  137. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  138. nat/llm/aws_bedrock_llm.py +24 -12
  139. nat/llm/azure_openai_llm.py +13 -6
  140. nat/llm/litellm_llm.py +69 -0
  141. nat/llm/nim_llm.py +20 -8
  142. nat/llm/openai_llm.py +14 -6
  143. nat/llm/register.py +4 -1
  144. nat/llm/utils/env_config_value.py +2 -3
  145. nat/llm/utils/thinking.py +215 -0
  146. nat/meta/pypi.md +9 -9
  147. nat/object_store/register.py +0 -1
  148. nat/observability/exporter/base_exporter.py +3 -3
  149. nat/observability/exporter/file_exporter.py +1 -1
  150. nat/observability/exporter/processing_exporter.py +309 -81
  151. nat/observability/exporter/span_exporter.py +35 -15
  152. nat/observability/exporter_manager.py +7 -7
  153. nat/observability/mixin/file_mixin.py +7 -7
  154. nat/observability/mixin/redaction_config_mixin.py +42 -0
  155. nat/observability/mixin/tagging_config_mixin.py +62 -0
  156. nat/observability/mixin/type_introspection_mixin.py +420 -107
  157. nat/observability/processor/batching_processor.py +5 -7
  158. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  159. nat/observability/processor/processor.py +3 -0
  160. nat/observability/processor/processor_factory.py +70 -0
  161. nat/observability/processor/redaction/__init__.py +24 -0
  162. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  163. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  164. nat/observability/processor/redaction/redaction_processor.py +177 -0
  165. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  166. nat/observability/processor/span_tagging_processor.py +68 -0
  167. nat/observability/register.py +6 -4
  168. nat/profiler/calc/calc_runner.py +3 -4
  169. nat/profiler/callbacks/agno_callback_handler.py +1 -1
  170. nat/profiler/callbacks/langchain_callback_handler.py +6 -6
  171. nat/profiler/callbacks/llama_index_callback_handler.py +3 -3
  172. nat/profiler/callbacks/semantic_kernel_callback_handler.py +3 -3
  173. nat/profiler/data_frame_row.py +1 -1
  174. nat/profiler/decorators/framework_wrapper.py +62 -13
  175. nat/profiler/decorators/function_tracking.py +160 -3
  176. nat/profiler/forecasting/models/forecasting_base_model.py +3 -1
  177. nat/profiler/forecasting/models/linear_model.py +1 -1
  178. nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
  179. nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
  180. nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +1 -1
  181. nat/profiler/inference_optimization/data_models.py +3 -3
  182. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +8 -9
  183. nat/profiler/inference_optimization/token_uniqueness.py +1 -1
  184. nat/profiler/parameter_optimization/__init__.py +0 -0
  185. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  186. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  187. nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
  188. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  189. nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
  190. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  191. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  192. nat/profiler/profile_runner.py +14 -9
  193. nat/profiler/utils.py +4 -2
  194. nat/registry_handlers/local/local_handler.py +2 -2
  195. nat/registry_handlers/package_utils.py +1 -2
  196. nat/registry_handlers/pypi/pypi_handler.py +23 -26
  197. nat/registry_handlers/register.py +3 -4
  198. nat/registry_handlers/rest/rest_handler.py +12 -13
  199. nat/retriever/milvus/retriever.py +2 -2
  200. nat/retriever/nemo_retriever/retriever.py +1 -1
  201. nat/retriever/register.py +0 -1
  202. nat/runtime/loader.py +2 -2
  203. nat/runtime/runner.py +106 -8
  204. nat/runtime/session.py +69 -8
  205. nat/settings/global_settings.py +16 -5
  206. nat/tool/chat_completion.py +5 -2
  207. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +3 -3
  208. nat/tool/datetime_tools.py +49 -9
  209. nat/tool/document_search.py +2 -2
  210. nat/tool/github_tools.py +450 -0
  211. nat/tool/memory_tools/get_memory_tool.py +1 -1
  212. nat/tool/nvidia_rag.py +1 -1
  213. nat/tool/register.py +2 -9
  214. nat/tool/retriever.py +3 -2
  215. nat/utils/callable_utils.py +70 -0
  216. nat/utils/data_models/schema_validator.py +3 -3
  217. nat/utils/decorators.py +210 -0
  218. nat/utils/exception_handlers/automatic_retries.py +104 -51
  219. nat/utils/exception_handlers/schemas.py +1 -1
  220. nat/utils/io/yaml_tools.py +2 -2
  221. nat/utils/log_levels.py +25 -0
  222. nat/utils/reactive/base/observable_base.py +2 -2
  223. nat/utils/reactive/base/observer_base.py +1 -1
  224. nat/utils/reactive/observable.py +2 -2
  225. nat/utils/reactive/observer.py +4 -4
  226. nat/utils/reactive/subscription.py +1 -1
  227. nat/utils/settings/global_settings.py +6 -8
  228. nat/utils/type_converter.py +4 -3
  229. nat/utils/type_utils.py +9 -5
  230. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/METADATA +42 -18
  231. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/RECORD +238 -196
  232. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/entry_points.txt +1 -0
  233. nat/cli/commands/info/list_mcp.py +0 -304
  234. nat/tool/github_tools/create_github_commit.py +0 -133
  235. nat/tool/github_tools/create_github_issue.py +0 -87
  236. nat/tool/github_tools/create_github_pr.py +0 -106
  237. nat/tool/github_tools/get_github_file.py +0 -106
  238. nat/tool/github_tools/get_github_issue.py +0 -166
  239. nat/tool/github_tools/get_github_pr.py +0 -256
  240. nat/tool/github_tools/update_github_issue.py +0 -100
  241. nat/tool/mcp/exceptions.py +0 -142
  242. nat/tool/mcp/mcp_client.py +0 -255
  243. nat/tool/mcp/mcp_tool.py +0 -96
  244. nat/utils/exception_handlers/mcp.py +0 -211
  245. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  246. /nat/{tool/mcp → authentication/credential_validator}/__init__.py +0 -0
  247. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/WHEEL +0 -0
  248. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  249. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/licenses/LICENSE.md +0 -0
  250. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.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,16 +1,17 @@
1
- general:
2
- use_uvloop: true
3
- logging:
4
- console:
5
- _type: console
6
- level: WARN
1
+ functions:
2
+ current_datetime:
3
+ _type: current_datetime
4
+ {{python_safe_workflow_name}}:
5
+ _type: {{python_safe_workflow_name}}
6
+ prefix: "Hello:"
7
7
 
8
- front_end:
9
- _type: fastapi
10
-
11
- front_end:
12
- _type: console
8
+ llms:
9
+ nim_llm:
10
+ _type: nim
11
+ model_name: meta/llama-3.1-70b-instruct
12
+ temperature: 0.0
13
13
 
14
14
  workflow:
15
- _type: {{workflow_name}}
16
- parameter: default_value
15
+ _type: react_agent
16
+ llm_name: nim_llm
17
+ tool_names: [current_datetime, {{python_safe_workflow_name}}]
@@ -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,5 +1,4 @@
1
- # pylint: disable=unused-import
2
1
  # flake8: noqa
3
2
 
4
- # Import any tools which need to be automatically registered here
5
- from {{package_name}} import {{workflow_name}}_function
3
+ # Import the generated workflow function to trigger registration
4
+ from .{{package_name}} import {{ python_safe_workflow_name }}_function
@@ -3,6 +3,7 @@ import logging
3
3
  from pydantic import Field
4
4
 
5
5
  from nat.builder.builder import Builder
6
+ from nat.builder.framework_enum import LLMFrameworkEnum
6
7
  from nat.builder.function_info import FunctionInfo
7
8
  from nat.cli.register_workflow import register_function
8
9
  from nat.data_models.function import FunctionBaseConfig
@@ -12,25 +13,38 @@ logger = logging.getLogger(__name__)
12
13
 
13
14
  class {{ workflow_class_name }}(FunctionBaseConfig, name="{{ workflow_name }}"):
14
15
  """
15
- {{workflow_description}}
16
+ {{ workflow_description }}
16
17
  """
17
- # Add your custom configuration parameters here
18
- parameter: str = Field(default="default_value", description="Notional description for this parameter")
19
-
20
-
21
- @register_function(config_type={{ workflow_class_name }})
22
- async def {{ python_safe_workflow_name }}_function(
23
- config: {{ workflow_class_name }}, builder: Builder
24
- ):
25
- # Implement your function logic here
26
- async def _response_fn(input_message: str) -> str:
27
- # Process the input_message and generate output
28
- output_message = f"Hello from {{ workflow_name }} workflow! You said: {input_message}"
29
- return output_message
30
-
31
- try:
32
- yield FunctionInfo.create(single_fn=_response_fn)
33
- except GeneratorExit:
34
- logger.warning("Function exited early!")
35
- finally:
36
- logger.info("Cleaning up {{ workflow_name }} workflow.")
18
+ prefix: str = Field(default="Echo:", description="Prefix to add before the echoed text.")
19
+
20
+
21
+ @register_function(config_type={{ workflow_class_name }}, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN])
22
+ async def {{ python_safe_workflow_name }}_function(config: {{ workflow_class_name }}, builder: Builder):
23
+ """
24
+ Registers a function (addressable via `{{ workflow_name }}` in the configuration).
25
+ This registration ensures a static mapping of the function type, `{{ workflow_name }}`, to the `{{ workflow_class_name }}` configuration object.
26
+
27
+ Args:
28
+ config ({{ workflow_class_name }}): The configuration for the function.
29
+ builder (Builder): The builder object.
30
+
31
+ Returns:
32
+ FunctionInfo: The function info object for the function.
33
+ """
34
+
35
+ # Define the function that will be registered.
36
+ async def _echo(text: str) -> str:
37
+ """
38
+ Takes a text input and echoes back with a pre-defined prefix.
39
+
40
+ Args:
41
+ text (str): The text to echo back.
42
+
43
+ Returns:
44
+ str: The text with the prefix.
45
+ """
46
+ return f"{config.prefix} {text}"
47
+
48
+ # The callable is wrapped in a FunctionInfo object.
49
+ # The description parameter is used to describe the function.
50
+ yield FunctionInfo.from_fn(_echo, description=_echo.__doc__)