nvidia-nat 1.1.0a20251020__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 (480) hide show
  1. aiq/__init__.py +66 -0
  2. nat/agent/__init__.py +0 -0
  3. nat/agent/base.py +265 -0
  4. nat/agent/dual_node.py +72 -0
  5. nat/agent/prompt_optimizer/__init__.py +0 -0
  6. nat/agent/prompt_optimizer/prompt.py +68 -0
  7. nat/agent/prompt_optimizer/register.py +149 -0
  8. nat/agent/react_agent/__init__.py +0 -0
  9. nat/agent/react_agent/agent.py +394 -0
  10. nat/agent/react_agent/output_parser.py +104 -0
  11. nat/agent/react_agent/prompt.py +44 -0
  12. nat/agent/react_agent/register.py +168 -0
  13. nat/agent/reasoning_agent/__init__.py +0 -0
  14. nat/agent/reasoning_agent/reasoning_agent.py +227 -0
  15. nat/agent/register.py +23 -0
  16. nat/agent/rewoo_agent/__init__.py +0 -0
  17. nat/agent/rewoo_agent/agent.py +593 -0
  18. nat/agent/rewoo_agent/prompt.py +107 -0
  19. nat/agent/rewoo_agent/register.py +175 -0
  20. nat/agent/tool_calling_agent/__init__.py +0 -0
  21. nat/agent/tool_calling_agent/agent.py +246 -0
  22. nat/agent/tool_calling_agent/register.py +129 -0
  23. nat/authentication/__init__.py +14 -0
  24. nat/authentication/api_key/__init__.py +14 -0
  25. nat/authentication/api_key/api_key_auth_provider.py +96 -0
  26. nat/authentication/api_key/api_key_auth_provider_config.py +124 -0
  27. nat/authentication/api_key/register.py +26 -0
  28. nat/authentication/credential_validator/__init__.py +14 -0
  29. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  30. nat/authentication/exceptions/__init__.py +14 -0
  31. nat/authentication/exceptions/api_key_exceptions.py +38 -0
  32. nat/authentication/http_basic_auth/__init__.py +0 -0
  33. nat/authentication/http_basic_auth/http_basic_auth_provider.py +81 -0
  34. nat/authentication/http_basic_auth/register.py +30 -0
  35. nat/authentication/interfaces.py +96 -0
  36. nat/authentication/oauth2/__init__.py +14 -0
  37. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +140 -0
  38. nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +39 -0
  39. nat/authentication/oauth2/oauth2_resource_server_config.py +124 -0
  40. nat/authentication/oauth2/register.py +25 -0
  41. nat/authentication/register.py +20 -0
  42. nat/builder/__init__.py +0 -0
  43. nat/builder/builder.py +317 -0
  44. nat/builder/component_utils.py +320 -0
  45. nat/builder/context.py +321 -0
  46. nat/builder/embedder.py +24 -0
  47. nat/builder/eval_builder.py +166 -0
  48. nat/builder/evaluator.py +29 -0
  49. nat/builder/framework_enum.py +25 -0
  50. nat/builder/front_end.py +73 -0
  51. nat/builder/function.py +714 -0
  52. nat/builder/function_base.py +380 -0
  53. nat/builder/function_info.py +625 -0
  54. nat/builder/intermediate_step_manager.py +206 -0
  55. nat/builder/llm.py +25 -0
  56. nat/builder/retriever.py +25 -0
  57. nat/builder/user_interaction_manager.py +78 -0
  58. nat/builder/workflow.py +160 -0
  59. nat/builder/workflow_builder.py +1365 -0
  60. nat/cli/__init__.py +14 -0
  61. nat/cli/cli_utils/__init__.py +0 -0
  62. nat/cli/cli_utils/config_override.py +231 -0
  63. nat/cli/cli_utils/validation.py +37 -0
  64. nat/cli/commands/__init__.py +0 -0
  65. nat/cli/commands/configure/__init__.py +0 -0
  66. nat/cli/commands/configure/channel/__init__.py +0 -0
  67. nat/cli/commands/configure/channel/add.py +28 -0
  68. nat/cli/commands/configure/channel/channel.py +34 -0
  69. nat/cli/commands/configure/channel/remove.py +30 -0
  70. nat/cli/commands/configure/channel/update.py +30 -0
  71. nat/cli/commands/configure/configure.py +33 -0
  72. nat/cli/commands/evaluate.py +139 -0
  73. nat/cli/commands/info/__init__.py +14 -0
  74. nat/cli/commands/info/info.py +47 -0
  75. nat/cli/commands/info/list_channels.py +32 -0
  76. nat/cli/commands/info/list_components.py +128 -0
  77. nat/cli/commands/mcp/__init__.py +14 -0
  78. nat/cli/commands/mcp/mcp.py +986 -0
  79. nat/cli/commands/object_store/__init__.py +14 -0
  80. nat/cli/commands/object_store/object_store.py +227 -0
  81. nat/cli/commands/optimize.py +90 -0
  82. nat/cli/commands/registry/__init__.py +14 -0
  83. nat/cli/commands/registry/publish.py +88 -0
  84. nat/cli/commands/registry/pull.py +118 -0
  85. nat/cli/commands/registry/registry.py +36 -0
  86. nat/cli/commands/registry/remove.py +108 -0
  87. nat/cli/commands/registry/search.py +153 -0
  88. nat/cli/commands/sizing/__init__.py +14 -0
  89. nat/cli/commands/sizing/calc.py +297 -0
  90. nat/cli/commands/sizing/sizing.py +27 -0
  91. nat/cli/commands/start.py +257 -0
  92. nat/cli/commands/uninstall.py +81 -0
  93. nat/cli/commands/validate.py +47 -0
  94. nat/cli/commands/workflow/__init__.py +14 -0
  95. nat/cli/commands/workflow/templates/__init__.py.j2 +0 -0
  96. nat/cli/commands/workflow/templates/config.yml.j2 +17 -0
  97. nat/cli/commands/workflow/templates/pyproject.toml.j2 +25 -0
  98. nat/cli/commands/workflow/templates/register.py.j2 +4 -0
  99. nat/cli/commands/workflow/templates/workflow.py.j2 +50 -0
  100. nat/cli/commands/workflow/workflow.py +37 -0
  101. nat/cli/commands/workflow/workflow_commands.py +403 -0
  102. nat/cli/entrypoint.py +141 -0
  103. nat/cli/main.py +60 -0
  104. nat/cli/register_workflow.py +522 -0
  105. nat/cli/type_registry.py +1069 -0
  106. nat/control_flow/__init__.py +0 -0
  107. nat/control_flow/register.py +20 -0
  108. nat/control_flow/router_agent/__init__.py +0 -0
  109. nat/control_flow/router_agent/agent.py +329 -0
  110. nat/control_flow/router_agent/prompt.py +48 -0
  111. nat/control_flow/router_agent/register.py +91 -0
  112. nat/control_flow/sequential_executor.py +166 -0
  113. nat/data_models/__init__.py +14 -0
  114. nat/data_models/agent.py +34 -0
  115. nat/data_models/api_server.py +843 -0
  116. nat/data_models/authentication.py +245 -0
  117. nat/data_models/common.py +171 -0
  118. nat/data_models/component.py +60 -0
  119. nat/data_models/component_ref.py +179 -0
  120. nat/data_models/config.py +434 -0
  121. nat/data_models/dataset_handler.py +169 -0
  122. nat/data_models/discovery_metadata.py +305 -0
  123. nat/data_models/embedder.py +27 -0
  124. nat/data_models/evaluate.py +130 -0
  125. nat/data_models/evaluator.py +26 -0
  126. nat/data_models/front_end.py +26 -0
  127. nat/data_models/function.py +64 -0
  128. nat/data_models/function_dependencies.py +80 -0
  129. nat/data_models/gated_field_mixin.py +242 -0
  130. nat/data_models/interactive.py +246 -0
  131. nat/data_models/intermediate_step.py +302 -0
  132. nat/data_models/invocation_node.py +38 -0
  133. nat/data_models/llm.py +27 -0
  134. nat/data_models/logging.py +26 -0
  135. nat/data_models/memory.py +27 -0
  136. nat/data_models/object_store.py +44 -0
  137. nat/data_models/optimizable.py +119 -0
  138. nat/data_models/optimizer.py +149 -0
  139. nat/data_models/profiler.py +54 -0
  140. nat/data_models/registry_handler.py +26 -0
  141. nat/data_models/retriever.py +30 -0
  142. nat/data_models/retry_mixin.py +35 -0
  143. nat/data_models/span.py +228 -0
  144. nat/data_models/step_adaptor.py +64 -0
  145. nat/data_models/streaming.py +33 -0
  146. nat/data_models/swe_bench_model.py +54 -0
  147. nat/data_models/telemetry_exporter.py +26 -0
  148. nat/data_models/temperature_mixin.py +44 -0
  149. nat/data_models/thinking_mixin.py +86 -0
  150. nat/data_models/top_p_mixin.py +44 -0
  151. nat/data_models/ttc_strategy.py +30 -0
  152. nat/embedder/__init__.py +0 -0
  153. nat/embedder/azure_openai_embedder.py +46 -0
  154. nat/embedder/nim_embedder.py +59 -0
  155. nat/embedder/openai_embedder.py +42 -0
  156. nat/embedder/register.py +22 -0
  157. nat/eval/__init__.py +14 -0
  158. nat/eval/config.py +62 -0
  159. nat/eval/dataset_handler/__init__.py +0 -0
  160. nat/eval/dataset_handler/dataset_downloader.py +106 -0
  161. nat/eval/dataset_handler/dataset_filter.py +52 -0
  162. nat/eval/dataset_handler/dataset_handler.py +431 -0
  163. nat/eval/evaluate.py +565 -0
  164. nat/eval/evaluator/__init__.py +14 -0
  165. nat/eval/evaluator/base_evaluator.py +77 -0
  166. nat/eval/evaluator/evaluator_model.py +58 -0
  167. nat/eval/intermediate_step_adapter.py +99 -0
  168. nat/eval/rag_evaluator/__init__.py +0 -0
  169. nat/eval/rag_evaluator/evaluate.py +178 -0
  170. nat/eval/rag_evaluator/register.py +143 -0
  171. nat/eval/register.py +26 -0
  172. nat/eval/remote_workflow.py +133 -0
  173. nat/eval/runners/__init__.py +14 -0
  174. nat/eval/runners/config.py +39 -0
  175. nat/eval/runners/multi_eval_runner.py +54 -0
  176. nat/eval/runtime_evaluator/__init__.py +14 -0
  177. nat/eval/runtime_evaluator/evaluate.py +123 -0
  178. nat/eval/runtime_evaluator/register.py +100 -0
  179. nat/eval/runtime_event_subscriber.py +52 -0
  180. nat/eval/swe_bench_evaluator/__init__.py +0 -0
  181. nat/eval/swe_bench_evaluator/evaluate.py +215 -0
  182. nat/eval/swe_bench_evaluator/register.py +36 -0
  183. nat/eval/trajectory_evaluator/__init__.py +0 -0
  184. nat/eval/trajectory_evaluator/evaluate.py +75 -0
  185. nat/eval/trajectory_evaluator/register.py +40 -0
  186. nat/eval/tunable_rag_evaluator/__init__.py +0 -0
  187. nat/eval/tunable_rag_evaluator/evaluate.py +242 -0
  188. nat/eval/tunable_rag_evaluator/register.py +52 -0
  189. nat/eval/usage_stats.py +41 -0
  190. nat/eval/utils/__init__.py +0 -0
  191. nat/eval/utils/eval_trace_ctx.py +89 -0
  192. nat/eval/utils/output_uploader.py +140 -0
  193. nat/eval/utils/tqdm_position_registry.py +40 -0
  194. nat/eval/utils/weave_eval.py +193 -0
  195. nat/experimental/__init__.py +0 -0
  196. nat/experimental/decorators/__init__.py +0 -0
  197. nat/experimental/decorators/experimental_warning_decorator.py +154 -0
  198. nat/experimental/test_time_compute/__init__.py +0 -0
  199. nat/experimental/test_time_compute/editing/__init__.py +0 -0
  200. nat/experimental/test_time_compute/editing/iterative_plan_refinement_editor.py +147 -0
  201. nat/experimental/test_time_compute/editing/llm_as_a_judge_editor.py +204 -0
  202. nat/experimental/test_time_compute/editing/motivation_aware_summarization.py +107 -0
  203. nat/experimental/test_time_compute/functions/__init__.py +0 -0
  204. nat/experimental/test_time_compute/functions/execute_score_select_function.py +105 -0
  205. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +228 -0
  206. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +205 -0
  207. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +146 -0
  208. nat/experimental/test_time_compute/models/__init__.py +0 -0
  209. nat/experimental/test_time_compute/models/editor_config.py +132 -0
  210. nat/experimental/test_time_compute/models/scoring_config.py +112 -0
  211. nat/experimental/test_time_compute/models/search_config.py +120 -0
  212. nat/experimental/test_time_compute/models/selection_config.py +154 -0
  213. nat/experimental/test_time_compute/models/stage_enums.py +43 -0
  214. nat/experimental/test_time_compute/models/strategy_base.py +67 -0
  215. nat/experimental/test_time_compute/models/tool_use_config.py +41 -0
  216. nat/experimental/test_time_compute/models/ttc_item.py +48 -0
  217. nat/experimental/test_time_compute/register.py +35 -0
  218. nat/experimental/test_time_compute/scoring/__init__.py +0 -0
  219. nat/experimental/test_time_compute/scoring/llm_based_agent_scorer.py +168 -0
  220. nat/experimental/test_time_compute/scoring/llm_based_plan_scorer.py +168 -0
  221. nat/experimental/test_time_compute/scoring/motivation_aware_scorer.py +111 -0
  222. nat/experimental/test_time_compute/search/__init__.py +0 -0
  223. nat/experimental/test_time_compute/search/multi_llm_planner.py +128 -0
  224. nat/experimental/test_time_compute/search/multi_query_retrieval_search.py +122 -0
  225. nat/experimental/test_time_compute/search/single_shot_multi_plan_planner.py +128 -0
  226. nat/experimental/test_time_compute/selection/__init__.py +0 -0
  227. nat/experimental/test_time_compute/selection/best_of_n_selector.py +63 -0
  228. nat/experimental/test_time_compute/selection/llm_based_agent_output_selector.py +131 -0
  229. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +157 -0
  230. nat/experimental/test_time_compute/selection/llm_based_plan_selector.py +128 -0
  231. nat/experimental/test_time_compute/selection/threshold_selector.py +58 -0
  232. nat/front_ends/__init__.py +14 -0
  233. nat/front_ends/console/__init__.py +14 -0
  234. nat/front_ends/console/authentication_flow_handler.py +285 -0
  235. nat/front_ends/console/console_front_end_config.py +32 -0
  236. nat/front_ends/console/console_front_end_plugin.py +108 -0
  237. nat/front_ends/console/register.py +25 -0
  238. nat/front_ends/cron/__init__.py +14 -0
  239. nat/front_ends/fastapi/__init__.py +14 -0
  240. nat/front_ends/fastapi/auth_flow_handlers/__init__.py +0 -0
  241. nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +27 -0
  242. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +142 -0
  243. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  244. nat/front_ends/fastapi/fastapi_front_end_config.py +272 -0
  245. nat/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
  246. nat/front_ends/fastapi/fastapi_front_end_plugin.py +247 -0
  247. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +1257 -0
  248. nat/front_ends/fastapi/html_snippets/__init__.py +14 -0
  249. nat/front_ends/fastapi/html_snippets/auth_code_grant_success.py +35 -0
  250. nat/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
  251. nat/front_ends/fastapi/job_store.py +602 -0
  252. nat/front_ends/fastapi/main.py +64 -0
  253. nat/front_ends/fastapi/message_handler.py +344 -0
  254. nat/front_ends/fastapi/message_validator.py +351 -0
  255. nat/front_ends/fastapi/register.py +25 -0
  256. nat/front_ends/fastapi/response_helpers.py +195 -0
  257. nat/front_ends/fastapi/step_adaptor.py +319 -0
  258. nat/front_ends/fastapi/utils.py +57 -0
  259. nat/front_ends/mcp/__init__.py +14 -0
  260. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  261. nat/front_ends/mcp/mcp_front_end_config.py +90 -0
  262. nat/front_ends/mcp/mcp_front_end_plugin.py +113 -0
  263. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +268 -0
  264. nat/front_ends/mcp/memory_profiler.py +320 -0
  265. nat/front_ends/mcp/register.py +27 -0
  266. nat/front_ends/mcp/tool_converter.py +290 -0
  267. nat/front_ends/register.py +21 -0
  268. nat/front_ends/simple_base/__init__.py +14 -0
  269. nat/front_ends/simple_base/simple_front_end_plugin_base.py +56 -0
  270. nat/llm/__init__.py +0 -0
  271. nat/llm/aws_bedrock_llm.py +69 -0
  272. nat/llm/azure_openai_llm.py +57 -0
  273. nat/llm/litellm_llm.py +69 -0
  274. nat/llm/nim_llm.py +58 -0
  275. nat/llm/openai_llm.py +54 -0
  276. nat/llm/register.py +27 -0
  277. nat/llm/utils/__init__.py +14 -0
  278. nat/llm/utils/env_config_value.py +93 -0
  279. nat/llm/utils/error.py +17 -0
  280. nat/llm/utils/thinking.py +215 -0
  281. nat/memory/__init__.py +20 -0
  282. nat/memory/interfaces.py +183 -0
  283. nat/memory/models.py +112 -0
  284. nat/meta/pypi.md +58 -0
  285. nat/object_store/__init__.py +20 -0
  286. nat/object_store/in_memory_object_store.py +76 -0
  287. nat/object_store/interfaces.py +84 -0
  288. nat/object_store/models.py +38 -0
  289. nat/object_store/register.py +19 -0
  290. nat/observability/__init__.py +14 -0
  291. nat/observability/exporter/__init__.py +14 -0
  292. nat/observability/exporter/base_exporter.py +449 -0
  293. nat/observability/exporter/exporter.py +78 -0
  294. nat/observability/exporter/file_exporter.py +33 -0
  295. nat/observability/exporter/processing_exporter.py +550 -0
  296. nat/observability/exporter/raw_exporter.py +52 -0
  297. nat/observability/exporter/span_exporter.py +308 -0
  298. nat/observability/exporter_manager.py +335 -0
  299. nat/observability/mixin/__init__.py +14 -0
  300. nat/observability/mixin/batch_config_mixin.py +26 -0
  301. nat/observability/mixin/collector_config_mixin.py +23 -0
  302. nat/observability/mixin/file_mixin.py +288 -0
  303. nat/observability/mixin/file_mode.py +23 -0
  304. nat/observability/mixin/redaction_config_mixin.py +42 -0
  305. nat/observability/mixin/resource_conflict_mixin.py +134 -0
  306. nat/observability/mixin/serialize_mixin.py +61 -0
  307. nat/observability/mixin/tagging_config_mixin.py +62 -0
  308. nat/observability/mixin/type_introspection_mixin.py +496 -0
  309. nat/observability/processor/__init__.py +14 -0
  310. nat/observability/processor/batching_processor.py +308 -0
  311. nat/observability/processor/callback_processor.py +42 -0
  312. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  313. nat/observability/processor/intermediate_step_serializer.py +28 -0
  314. nat/observability/processor/processor.py +74 -0
  315. nat/observability/processor/processor_factory.py +70 -0
  316. nat/observability/processor/redaction/__init__.py +24 -0
  317. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  318. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  319. nat/observability/processor/redaction/redaction_processor.py +177 -0
  320. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  321. nat/observability/processor/span_tagging_processor.py +68 -0
  322. nat/observability/register.py +114 -0
  323. nat/observability/utils/__init__.py +14 -0
  324. nat/observability/utils/dict_utils.py +236 -0
  325. nat/observability/utils/time_utils.py +31 -0
  326. nat/plugins/.namespace +1 -0
  327. nat/profiler/__init__.py +0 -0
  328. nat/profiler/calc/__init__.py +14 -0
  329. nat/profiler/calc/calc_runner.py +626 -0
  330. nat/profiler/calc/calculations.py +288 -0
  331. nat/profiler/calc/data_models.py +188 -0
  332. nat/profiler/calc/plot.py +345 -0
  333. nat/profiler/callbacks/__init__.py +0 -0
  334. nat/profiler/callbacks/agno_callback_handler.py +295 -0
  335. nat/profiler/callbacks/base_callback_class.py +20 -0
  336. nat/profiler/callbacks/langchain_callback_handler.py +297 -0
  337. nat/profiler/callbacks/llama_index_callback_handler.py +205 -0
  338. nat/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
  339. nat/profiler/callbacks/token_usage_base_model.py +27 -0
  340. nat/profiler/data_frame_row.py +51 -0
  341. nat/profiler/data_models.py +24 -0
  342. nat/profiler/decorators/__init__.py +0 -0
  343. nat/profiler/decorators/framework_wrapper.py +180 -0
  344. nat/profiler/decorators/function_tracking.py +411 -0
  345. nat/profiler/forecasting/__init__.py +0 -0
  346. nat/profiler/forecasting/config.py +18 -0
  347. nat/profiler/forecasting/model_trainer.py +75 -0
  348. nat/profiler/forecasting/models/__init__.py +22 -0
  349. nat/profiler/forecasting/models/forecasting_base_model.py +42 -0
  350. nat/profiler/forecasting/models/linear_model.py +197 -0
  351. nat/profiler/forecasting/models/random_forest_regressor.py +269 -0
  352. nat/profiler/inference_metrics_model.py +28 -0
  353. nat/profiler/inference_optimization/__init__.py +0 -0
  354. nat/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
  355. nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +460 -0
  356. nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
  357. nat/profiler/inference_optimization/data_models.py +386 -0
  358. nat/profiler/inference_optimization/experimental/__init__.py +0 -0
  359. nat/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
  360. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +404 -0
  361. nat/profiler/inference_optimization/llm_metrics.py +212 -0
  362. nat/profiler/inference_optimization/prompt_caching.py +163 -0
  363. nat/profiler/inference_optimization/token_uniqueness.py +107 -0
  364. nat/profiler/inference_optimization/workflow_runtimes.py +72 -0
  365. nat/profiler/intermediate_property_adapter.py +102 -0
  366. nat/profiler/parameter_optimization/__init__.py +0 -0
  367. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  368. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  369. nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
  370. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  371. nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
  372. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  373. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  374. nat/profiler/profile_runner.py +478 -0
  375. nat/profiler/utils.py +186 -0
  376. nat/registry_handlers/__init__.py +0 -0
  377. nat/registry_handlers/local/__init__.py +0 -0
  378. nat/registry_handlers/local/local_handler.py +176 -0
  379. nat/registry_handlers/local/register_local.py +37 -0
  380. nat/registry_handlers/metadata_factory.py +60 -0
  381. nat/registry_handlers/package_utils.py +570 -0
  382. nat/registry_handlers/pypi/__init__.py +0 -0
  383. nat/registry_handlers/pypi/pypi_handler.py +248 -0
  384. nat/registry_handlers/pypi/register_pypi.py +40 -0
  385. nat/registry_handlers/register.py +20 -0
  386. nat/registry_handlers/registry_handler_base.py +157 -0
  387. nat/registry_handlers/rest/__init__.py +0 -0
  388. nat/registry_handlers/rest/register_rest.py +56 -0
  389. nat/registry_handlers/rest/rest_handler.py +236 -0
  390. nat/registry_handlers/schemas/__init__.py +0 -0
  391. nat/registry_handlers/schemas/headers.py +42 -0
  392. nat/registry_handlers/schemas/package.py +68 -0
  393. nat/registry_handlers/schemas/publish.py +68 -0
  394. nat/registry_handlers/schemas/pull.py +82 -0
  395. nat/registry_handlers/schemas/remove.py +36 -0
  396. nat/registry_handlers/schemas/search.py +91 -0
  397. nat/registry_handlers/schemas/status.py +47 -0
  398. nat/retriever/__init__.py +0 -0
  399. nat/retriever/interface.py +41 -0
  400. nat/retriever/milvus/__init__.py +14 -0
  401. nat/retriever/milvus/register.py +81 -0
  402. nat/retriever/milvus/retriever.py +228 -0
  403. nat/retriever/models.py +77 -0
  404. nat/retriever/nemo_retriever/__init__.py +14 -0
  405. nat/retriever/nemo_retriever/register.py +60 -0
  406. nat/retriever/nemo_retriever/retriever.py +190 -0
  407. nat/retriever/register.py +21 -0
  408. nat/runtime/__init__.py +14 -0
  409. nat/runtime/loader.py +220 -0
  410. nat/runtime/runner.py +292 -0
  411. nat/runtime/session.py +223 -0
  412. nat/runtime/user_metadata.py +130 -0
  413. nat/settings/__init__.py +0 -0
  414. nat/settings/global_settings.py +329 -0
  415. nat/test/.namespace +1 -0
  416. nat/tool/__init__.py +0 -0
  417. nat/tool/chat_completion.py +77 -0
  418. nat/tool/code_execution/README.md +151 -0
  419. nat/tool/code_execution/__init__.py +0 -0
  420. nat/tool/code_execution/code_sandbox.py +267 -0
  421. nat/tool/code_execution/local_sandbox/.gitignore +1 -0
  422. nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
  423. nat/tool/code_execution/local_sandbox/__init__.py +13 -0
  424. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +198 -0
  425. nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +6 -0
  426. nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +50 -0
  427. nat/tool/code_execution/register.py +74 -0
  428. nat/tool/code_execution/test_code_execution_sandbox.py +414 -0
  429. nat/tool/code_execution/utils.py +100 -0
  430. nat/tool/datetime_tools.py +82 -0
  431. nat/tool/document_search.py +141 -0
  432. nat/tool/github_tools.py +450 -0
  433. nat/tool/memory_tools/__init__.py +0 -0
  434. nat/tool/memory_tools/add_memory_tool.py +79 -0
  435. nat/tool/memory_tools/delete_memory_tool.py +66 -0
  436. nat/tool/memory_tools/get_memory_tool.py +72 -0
  437. nat/tool/nvidia_rag.py +95 -0
  438. nat/tool/register.py +31 -0
  439. nat/tool/retriever.py +95 -0
  440. nat/tool/server_tools.py +66 -0
  441. nat/utils/__init__.py +0 -0
  442. nat/utils/callable_utils.py +70 -0
  443. nat/utils/data_models/__init__.py +0 -0
  444. nat/utils/data_models/schema_validator.py +58 -0
  445. nat/utils/debugging_utils.py +43 -0
  446. nat/utils/decorators.py +210 -0
  447. nat/utils/dump_distro_mapping.py +32 -0
  448. nat/utils/exception_handlers/__init__.py +0 -0
  449. nat/utils/exception_handlers/automatic_retries.py +342 -0
  450. nat/utils/exception_handlers/schemas.py +114 -0
  451. nat/utils/io/__init__.py +0 -0
  452. nat/utils/io/model_processing.py +28 -0
  453. nat/utils/io/yaml_tools.py +119 -0
  454. nat/utils/log_levels.py +25 -0
  455. nat/utils/log_utils.py +37 -0
  456. nat/utils/metadata_utils.py +74 -0
  457. nat/utils/optional_imports.py +142 -0
  458. nat/utils/producer_consumer_queue.py +178 -0
  459. nat/utils/reactive/__init__.py +0 -0
  460. nat/utils/reactive/base/__init__.py +0 -0
  461. nat/utils/reactive/base/observable_base.py +65 -0
  462. nat/utils/reactive/base/observer_base.py +55 -0
  463. nat/utils/reactive/base/subject_base.py +79 -0
  464. nat/utils/reactive/observable.py +59 -0
  465. nat/utils/reactive/observer.py +76 -0
  466. nat/utils/reactive/subject.py +131 -0
  467. nat/utils/reactive/subscription.py +49 -0
  468. nat/utils/settings/__init__.py +0 -0
  469. nat/utils/settings/global_settings.py +195 -0
  470. nat/utils/string_utils.py +38 -0
  471. nat/utils/type_converter.py +299 -0
  472. nat/utils/type_utils.py +488 -0
  473. nat/utils/url_utils.py +27 -0
  474. nvidia_nat-1.1.0a20251020.dist-info/METADATA +195 -0
  475. nvidia_nat-1.1.0a20251020.dist-info/RECORD +480 -0
  476. nvidia_nat-1.1.0a20251020.dist-info/WHEEL +5 -0
  477. nvidia_nat-1.1.0a20251020.dist-info/entry_points.txt +22 -0
  478. nvidia_nat-1.1.0a20251020.dist-info/licenses/LICENSE-3rd-party.txt +5478 -0
  479. nvidia_nat-1.1.0a20251020.dist-info/licenses/LICENSE.md +201 -0
  480. nvidia_nat-1.1.0a20251020.dist-info/top_level.txt +2 -0
@@ -0,0 +1,602 @@
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 json
17
+ import logging
18
+ import os
19
+ import shutil
20
+ import typing
21
+ from asyncio import current_task
22
+ from collections.abc import AsyncGenerator
23
+ from collections.abc import Callable
24
+ from contextlib import asynccontextmanager
25
+ from datetime import UTC
26
+ from datetime import datetime
27
+ from datetime import timedelta
28
+ from enum import Enum
29
+ from uuid import uuid4
30
+
31
+ from dask.distributed import Client as DaskClient
32
+ from dask.distributed import Future
33
+ from dask.distributed import Variable
34
+ from dask.distributed import fire_and_forget
35
+ from pydantic import BaseModel
36
+ from sqlalchemy import DateTime
37
+ from sqlalchemy import String
38
+ from sqlalchemy import and_
39
+ from sqlalchemy import select
40
+ from sqlalchemy import update
41
+ from sqlalchemy.ext.asyncio import async_scoped_session
42
+ from sqlalchemy.ext.asyncio import async_sessionmaker
43
+ from sqlalchemy.orm import DeclarativeBase
44
+ from sqlalchemy.orm import Mapped
45
+ from sqlalchemy.orm import mapped_column
46
+ from sqlalchemy.sql import expression as sa_expr
47
+
48
+ from nat.front_ends.fastapi.dask_client_mixin import DaskClientMixin
49
+
50
+ if typing.TYPE_CHECKING:
51
+ from sqlalchemy.engine import Engine
52
+ from sqlalchemy.ext.asyncio import AsyncEngine
53
+ from sqlalchemy.ext.asyncio import AsyncSession
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+
58
+ class JobStatus(str, Enum):
59
+ """
60
+ Enumeration of possible job statuses in the job store.
61
+
62
+ Attributes
63
+ ----------
64
+ SUBMITTED : str
65
+ Job has been submitted to the scheduler but not yet started.
66
+ RUNNING : str
67
+ Job is currently being executed.
68
+ SUCCESS : str
69
+ Job completed successfully.
70
+ FAILURE : str
71
+ Job failed during execution.
72
+ INTERRUPTED : str
73
+ Job was interrupted or cancelled before completion.
74
+ NOT_FOUND : str
75
+ Job ID does not exist in the job store.
76
+ """
77
+ SUBMITTED = "submitted"
78
+ RUNNING = "running"
79
+ SUCCESS = "success"
80
+ FAILURE = "failure"
81
+ INTERRUPTED = "interrupted"
82
+ NOT_FOUND = "not_found"
83
+
84
+
85
+ class Base(DeclarativeBase):
86
+ pass
87
+
88
+
89
+ class JobInfo(Base):
90
+ """
91
+ SQLAlchemy model representing job metadata and status information.
92
+
93
+ This model stores comprehensive information about jobs submitted to the Dask scheduler, including their current
94
+ status, configuration, outputs, and lifecycle metadata.
95
+
96
+ Attributes
97
+ ----------
98
+ job_id : str
99
+ Unique identifier for the job (primary key).
100
+ status : JobStatus
101
+ Current status of the job.
102
+ config_file : str, optional
103
+ Path to the configuration file used for the job.
104
+ error : str, optional
105
+ Error message if the job failed.
106
+ output_path : str, optional
107
+ Path where job outputs are stored.
108
+ created_at : datetime
109
+ Timestamp when the job was created.
110
+ updated_at : datetime
111
+ Timestamp when the job was last updated.
112
+ expiry_seconds : int
113
+ Number of seconds after which the job is eligible for cleanup.
114
+ output : str, optional
115
+ Serialized job output data (JSON format).
116
+ is_expired : bool
117
+ Flag indicating if the job has been marked as expired.
118
+ """
119
+ __tablename__ = "job_info"
120
+
121
+ job_id: Mapped[str] = mapped_column(primary_key=True)
122
+ status: Mapped[JobStatus] = mapped_column(String(11))
123
+ config_file: Mapped[str] = mapped_column(nullable=True)
124
+ error: Mapped[str] = mapped_column(nullable=True)
125
+ output_path: Mapped[str] = mapped_column(nullable=True)
126
+ created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.now(UTC))
127
+ updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True),
128
+ default=datetime.now(UTC),
129
+ onupdate=datetime.now(UTC))
130
+ expiry_seconds: Mapped[int]
131
+ output: Mapped[str] = mapped_column(nullable=True)
132
+ is_expired: Mapped[bool] = mapped_column(default=False, index=True)
133
+
134
+ def __repr__(self):
135
+ return f"JobInfo(job_id={self.job_id}, status={self.status})"
136
+
137
+
138
+ class JobStore(DaskClientMixin):
139
+ """
140
+ Tracks and manages jobs submitted to the Dask scheduler, along with persisting job metadata (JobInfo objects) in a
141
+ database.
142
+
143
+ Parameters
144
+ ----------
145
+ scheduler_address: str
146
+ The address of the Dask scheduler.
147
+ db_engine: AsyncEngine | None, optional, default=None
148
+ The database engine for the job store.
149
+ db_url: str | None, optional, default=None
150
+ The database URL to connect to, used when db_engine is not provided. Refer to:
151
+ https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls
152
+ """
153
+
154
+ MIN_EXPIRY = 600 # 10 minutes
155
+ MAX_EXPIRY = 86400 # 24 hours
156
+ DEFAULT_EXPIRY = 3600 # 1 hour
157
+
158
+ # active jobs are exempt from expiry
159
+ ACTIVE_STATUS = {JobStatus.RUNNING, JobStatus.SUBMITTED}
160
+
161
+ def __init__(
162
+ self,
163
+ scheduler_address: str,
164
+ db_engine: "AsyncEngine | None" = None,
165
+ db_url: str | None = None,
166
+ ):
167
+ self._scheduler_address = scheduler_address
168
+
169
+ if db_engine is None:
170
+ if db_url is None:
171
+ raise ValueError("Either db_engine or db_url must be provided")
172
+
173
+ db_engine = get_db_engine(db_url, use_async=True)
174
+
175
+ # Disabling expire_on_commit allows us to detach (expunge) job
176
+ # instances from the session
177
+ session_maker = async_sessionmaker(db_engine, expire_on_commit=False)
178
+
179
+ # The async_scoped_session ensures that the same session is used
180
+ # within the same task, and that no two tasks share the same session.
181
+ self._session = async_scoped_session(session_maker, scopefunc=current_task)
182
+
183
+ @asynccontextmanager
184
+ async def client(self) -> AsyncGenerator[DaskClient]:
185
+ """
186
+ Async context manager for obtaining a Dask client connection.
187
+
188
+ Yields
189
+ ------
190
+ DaskClient
191
+ An active Dask client connected to the scheduler. The client is automatically closed when exiting the
192
+ context manager.
193
+ """
194
+ async with super().client(self._scheduler_address) as client:
195
+ yield client
196
+
197
+ @asynccontextmanager
198
+ async def session(self) -> AsyncGenerator["AsyncSession"]:
199
+ """
200
+ Async context manager for a SQLAlchemy session with automatic transaction management.
201
+
202
+ Creates a new database session scoped to the current async task and begins a transaction. The transaction is
203
+ committed on successful exit and rolled back on exception. The session is automatically removed from the
204
+ registry after use.
205
+
206
+ Yields
207
+ ------
208
+ AsyncSession
209
+ An active SQLAlchemy async session with an open transaction.
210
+ """
211
+ async with self._session() as session:
212
+ async with session.begin():
213
+ yield session
214
+
215
+ # Removes the current task key from the session registry, preventing
216
+ # potential memory leaks
217
+ await self._session.remove()
218
+
219
+ def ensure_job_id(self, job_id: str | None) -> str:
220
+ """
221
+ Ensure a job ID is provided, generating a new one if necessary.
222
+
223
+ If a job ID is provided, it is returned as-is.
224
+
225
+ Parameters
226
+ ----------
227
+ job_id: str | None
228
+ The job ID to ensure, or None to generate a new one.
229
+ """
230
+ if job_id is None:
231
+ job_id = str(uuid4())
232
+ logger.info("Generated new job ID: %s", job_id)
233
+
234
+ return job_id
235
+
236
+ async def _create_job(self,
237
+ config_file: str | None = None,
238
+ job_id: str | None = None,
239
+ expiry_seconds: int = DEFAULT_EXPIRY) -> str:
240
+ """
241
+ Create a job and add it to the job store. This should not be called directly, but instead be called by
242
+ `submit_job`
243
+ """
244
+ job_id = self.ensure_job_id(job_id)
245
+
246
+ clamped_expiry = max(self.MIN_EXPIRY, min(expiry_seconds, self.MAX_EXPIRY))
247
+ if expiry_seconds != clamped_expiry:
248
+ logger.info(
249
+ "Clamped expiry_seconds from %d to %d for job %s",
250
+ expiry_seconds,
251
+ clamped_expiry,
252
+ job_id,
253
+ )
254
+
255
+ job = JobInfo(job_id=job_id,
256
+ status=JobStatus.SUBMITTED,
257
+ config_file=config_file,
258
+ created_at=datetime.now(UTC),
259
+ updated_at=datetime.now(UTC),
260
+ error=None,
261
+ output_path=None,
262
+ expiry_seconds=clamped_expiry)
263
+
264
+ async with self.session() as session:
265
+ session.add(job)
266
+
267
+ logger.info("Created new job %s with config %s", job_id, config_file)
268
+ return job_id
269
+
270
+ async def submit_job(self,
271
+ *,
272
+ job_id: str | None = None,
273
+ config_file: str | None = None,
274
+ expiry_seconds: int = DEFAULT_EXPIRY,
275
+ sync_timeout: int = 0,
276
+ job_fn: Callable[..., typing.Any],
277
+ job_args: list[typing.Any],
278
+ **job_kwargs) -> tuple[str, JobInfo | None]:
279
+ """
280
+ Submit a job to the Dask scheduler, and store job metadata in the database.
281
+
282
+ Parameters
283
+ ----------
284
+ job_id: str | None, optional, default=None
285
+ The job ID to use, or None to generate a new one.
286
+ config_file: str | None, optional, default=None
287
+ The config file used to run the job, if any.
288
+ expiry_seconds: int, optional, default=3600
289
+ The number of seconds after which the job should be considered expired. Expired jobs are eligible for
290
+ cleanup, but are not deleted immediately.
291
+ sync_timeout: int, optional, default=0
292
+ If greater than 0, wait for the job to complete for up to this many seconds. If the job does not complete
293
+ in this time, return immediately with the job ID and no job info. If the job completes in this time,
294
+ return the job ID and the job info. If 0, return immediately with the job ID and no job info.
295
+ job_fn: Callable[..., typing.Any]
296
+ The function to run as the job. This function must be serializable by Dask.
297
+ job_args: list[typing.Any]
298
+ The arguments to pass to the job function. These must be serializable by Dask.
299
+ job_kwargs: dict[str, typing.Any]
300
+ The keyword arguments to pass to the job function. These must be serializable by Dask
301
+ """
302
+ job_id = await self._create_job(job_id=job_id, config_file=config_file, expiry_seconds=expiry_seconds)
303
+
304
+ # We are intentionally not using job_id as the key, since Dask will clear the associated metadata once
305
+ # the job has completed, and we want the metadata to persist until the job expires.
306
+ async with self.client() as client:
307
+ logger.debug("Submitting job with job_args: %s, job_kwargs: %s", job_args, job_kwargs)
308
+ future = client.submit(job_fn, *job_args, key=f"{job_id}-job", **job_kwargs)
309
+
310
+ # Store the future in a variable, this allows us to potentially cancel the future later if needed
311
+ future_var = Variable(name=job_id, client=client)
312
+ await future_var.set(future)
313
+
314
+ if sync_timeout > 0:
315
+ try:
316
+ _ = await future.result(timeout=sync_timeout)
317
+ job = await self.get_job(job_id)
318
+ assert job is not None, "Job should exist after future result"
319
+ return (job_id, job)
320
+ except TimeoutError:
321
+ pass
322
+
323
+ fire_and_forget(future)
324
+
325
+ return (job_id, None)
326
+
327
+ async def update_status(self,
328
+ job_id: str,
329
+ status: str | JobStatus,
330
+ error: str | None = None,
331
+ output_path: str | None = None,
332
+ output: BaseModel | None = None):
333
+ """
334
+ Update the status and metadata of an existing job.
335
+
336
+ Parameters
337
+ ----------
338
+ job_id : str
339
+ The unique identifier of the job to update.
340
+ status : str | JobStatus
341
+ The new status to set for the job (should be a valid JobStatus value).
342
+ error : str, optional, default=None
343
+ Error message to store if the job failed.
344
+ output_path : str, optional, default=None
345
+ Path where job outputs are stored.
346
+ output : BaseModel, optional, default=None
347
+ Job output data. Can be a Pydantic BaseModel, dict, list, or string. BaseModel and dict/list objects are
348
+ serialized to JSON for storage.
349
+
350
+ Raises
351
+ ------
352
+ ValueError
353
+ If the specified job_id does not exist in the job store.
354
+ """
355
+
356
+ async with self.session() as session:
357
+ job: JobInfo = await session.get(JobInfo, job_id)
358
+ if job is None:
359
+ raise ValueError(f"Job {job_id} not found in job store")
360
+
361
+ if not isinstance(status, JobStatus):
362
+ status = JobStatus(status)
363
+
364
+ job.status = status.value
365
+ job.error = error
366
+ job.output_path = output_path
367
+ job.updated_at = datetime.now(UTC)
368
+
369
+ if isinstance(output, BaseModel):
370
+ # Convert BaseModel to JSON string for storage
371
+ output = output.model_dump_json(round_trip=True)
372
+
373
+ if isinstance(output, dict | list):
374
+ # Convert dict or list to JSON string for storage
375
+ output = json.dumps(output)
376
+
377
+ job.output = output
378
+
379
+ async def get_all_jobs(self) -> list[JobInfo]:
380
+ """
381
+ Retrieve all jobs from the job store.
382
+
383
+ Returns
384
+ -------
385
+ list[JobInfo]
386
+ A list of all JobInfo objects in the database. This operation can be expensive if there are many jobs
387
+ stored.
388
+
389
+ Warning
390
+ -------
391
+ This method loads all jobs into memory and should be used with caution in production environments with large
392
+ job stores.
393
+ """
394
+ async with self.session() as session:
395
+ return (await session.scalars(select(JobInfo))).all()
396
+
397
+ async def get_job(self, job_id: str) -> JobInfo | None:
398
+ """
399
+ Retrieve a specific job by its unique identifier.
400
+
401
+ Parameters
402
+ ----------
403
+ job_id : str
404
+ The unique identifier of the job to retrieve.
405
+
406
+ Returns
407
+ -------
408
+ JobInfo or None
409
+ The JobInfo object if found, None if the job_id does not exist.
410
+ """
411
+ async with self.session() as session:
412
+ return await session.get(JobInfo, job_id)
413
+
414
+ async def get_status(self, job_id: str) -> JobStatus:
415
+ """
416
+ Get the current status of a specific job.
417
+
418
+ Parameters
419
+ ----------
420
+ job_id : str
421
+ The unique identifier of the job.
422
+
423
+ Returns
424
+ -------
425
+ JobStatus
426
+ The current status of the job, or JobStatus.NOT_FOUND if the job does not exist in the store.
427
+ """
428
+ job = await self.get_job(job_id)
429
+ if job is not None:
430
+ return JobStatus(job.status)
431
+ else:
432
+ return JobStatus.NOT_FOUND
433
+
434
+ async def get_last_job(self) -> JobInfo | None:
435
+ """
436
+ Retrieve the most recently created job.
437
+
438
+ Returns
439
+ -------
440
+ JobInfo or None
441
+ The JobInfo object for the most recently created job based on the created_at timestamp, or None if no jobs
442
+ exist in the store.
443
+ """
444
+ stmt = select(JobInfo).order_by(JobInfo.created_at.desc())
445
+ async with self.session() as session:
446
+ last_job = (await session.scalars(stmt)).first()
447
+
448
+ if last_job is None:
449
+ logger.info("No jobs found in job store")
450
+ else:
451
+ logger.info("Retrieved last job %s created at %s", last_job.job_id, last_job.created_at)
452
+
453
+ return last_job
454
+
455
+ async def get_jobs_by_status(self, status: str | JobStatus) -> list[JobInfo]:
456
+ """
457
+ Retrieve all jobs that have a specific status.
458
+
459
+ Parameters
460
+ ----------
461
+ status : str | JobStatus
462
+ The status to filter jobs by.
463
+
464
+ Returns
465
+ -------
466
+ list[JobInfo]
467
+ A list of JobInfo objects that have the specified status. Returns an empty list if no jobs match the
468
+ status.
469
+ """
470
+ if not isinstance(status, JobStatus):
471
+ status = JobStatus(status)
472
+
473
+ stmt = select(JobInfo).where(JobInfo.status == status)
474
+ async with self.session() as session:
475
+ return (await session.scalars(stmt)).all()
476
+
477
+ def get_expires_at(self, job: JobInfo) -> datetime | None:
478
+ """
479
+ Calculate the expiration time for a given job.
480
+
481
+ Active jobs (with status in `self.ACTIVE_STATUS`) do not expire and return `None`. For non-active jobs, the
482
+ expiration time is calculated as updated_at + expiry_seconds.
483
+
484
+ Parameters
485
+ ----------
486
+ job : JobInfo
487
+ The job object to calculate expiration time for.
488
+
489
+ Returns
490
+ -------
491
+ datetime or None
492
+ The UTC datetime when the job will expire, or None if the job is active and therefore exempt from
493
+ expiration.
494
+ """
495
+ if job.status in self.ACTIVE_STATUS:
496
+ return None
497
+
498
+ updated_at = job.updated_at
499
+ if updated_at.tzinfo is None:
500
+ # Not all DB backends support timezone aware datetimes
501
+ updated_at = updated_at.replace(tzinfo=UTC)
502
+
503
+ return updated_at + timedelta(seconds=job.expiry_seconds)
504
+
505
+ async def cleanup_expired_jobs(self):
506
+ """
507
+ Cleanup expired jobs, keeping the most recent one.
508
+
509
+ Updated_at is used instead of created_at to determine the most recent job. This is because jobs may not be
510
+ processed in the order they are created.
511
+ """
512
+ now = datetime.now(UTC)
513
+
514
+ stmt = select(JobInfo).where(
515
+ and_(JobInfo.is_expired == sa_expr.false(),
516
+ JobInfo.status.not_in(self.ACTIVE_STATUS))).order_by(JobInfo.updated_at.desc())
517
+ # Filter out active jobs
518
+ async with (self.client() as client, self.session() as session):
519
+ finished_jobs = (await session.execute(stmt)).scalars().all()
520
+
521
+ # Always keep the most recent finished job
522
+ jobs_to_check = finished_jobs[1:]
523
+
524
+ expired_ids = []
525
+ for job in jobs_to_check:
526
+ expires_at = self.get_expires_at(job)
527
+ if expires_at and now > expires_at:
528
+ expired_ids.append(job.job_id)
529
+ # cleanup output dir if present
530
+ if job.output_path:
531
+ logger.info("Cleaning up output directory for job %s at %s", job.job_id, job.output_path)
532
+ # If it is a file remove it
533
+ if os.path.isfile(job.output_path):
534
+ os.remove(job.output_path)
535
+ # If it is a directory remove it
536
+ elif os.path.isdir(job.output_path):
537
+ shutil.rmtree(job.output_path)
538
+
539
+ if len(expired_ids) > 0:
540
+ successfully_expired = []
541
+ for job_id in expired_ids:
542
+ try:
543
+ var = Variable(name=job_id, client=client)
544
+ try:
545
+ future = await var.get(timeout=5)
546
+ if isinstance(future, Future):
547
+ await client.cancel([future], asynchronous=True, force=True)
548
+
549
+ except TimeoutError:
550
+ pass
551
+
552
+ var.delete()
553
+ successfully_expired.append(job_id)
554
+ except Exception:
555
+ logger.exception("Failed to expire %s", job_id)
556
+
557
+ await session.execute(
558
+ update(JobInfo).where(JobInfo.job_id.in_(successfully_expired)).values(is_expired=True))
559
+
560
+
561
+ def get_db_engine(db_url: str | None = None, echo: bool = False, use_async: bool = True) -> "Engine | AsyncEngine":
562
+ """
563
+ Create a SQLAlchemy database engine, this should only be run once per process
564
+
565
+ Parameters
566
+ ----------
567
+ db_url: str | None, optional, default=None
568
+ The database URL to connect to. Refer to https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls
569
+ echo: bool, optional, default=False
570
+ If True, SQLAlchemy will log all SQL statements. Useful for debugging.
571
+ use_async: bool, optional, default=True
572
+ If True, use the async database engine. The JobStore class requires an async database engine, setting
573
+ `use_async` to False is only useful for testing.
574
+ """
575
+ if db_url is None:
576
+ db_url = os.environ.get("NAT_JOB_STORE_DB_URL")
577
+ if db_url is None:
578
+ dot_tmp_dir = os.path.join(os.getcwd(), ".tmp")
579
+ os.makedirs(dot_tmp_dir, exist_ok=True)
580
+ db_file = os.path.join(dot_tmp_dir, "job_store.db")
581
+ if os.path.exists(db_file):
582
+ logger.warning("Database file %s already exists, it will be overwritten.", db_file)
583
+ os.remove(db_file)
584
+
585
+ if use_async:
586
+ driver = "+aiosqlite"
587
+ else:
588
+ driver = ""
589
+
590
+ db_url = f"sqlite{driver}:///{db_file}"
591
+
592
+ if use_async:
593
+ # This is actually a blocking call, it just returns an AsyncEngine
594
+ from sqlalchemy.ext.asyncio import create_async_engine as create_engine_fn
595
+ else:
596
+ from sqlalchemy import create_engine as create_engine_fn
597
+
598
+ return create_engine_fn(db_url, echo=echo)
599
+
600
+
601
+ # Prevent Sphinx from attempting to document the Base class which produces warnings
602
+ __all__ = ["get_db_engine", "JobInfo", "JobStatus", "JobStore"]
@@ -0,0 +1,64 @@
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 logging
17
+ import os
18
+ import typing
19
+
20
+ from nat.front_ends.fastapi.fastapi_front_end_plugin_worker import FastApiFrontEndPluginWorkerBase
21
+ from nat.front_ends.fastapi.utils import get_config_file_path
22
+ from nat.front_ends.fastapi.utils import import_class_from_string
23
+ from nat.runtime.loader import load_config
24
+
25
+ if typing.TYPE_CHECKING:
26
+ from fastapi import FastAPI
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def get_app() -> "FastAPI":
32
+
33
+ config_file_path = get_config_file_path()
34
+ front_end_worker_full_name = os.getenv("NAT_FRONT_END_WORKER")
35
+
36
+ if (not config_file_path):
37
+ raise ValueError("Config file not found in environment variable NAT_CONFIG_FILE.")
38
+
39
+ if (not front_end_worker_full_name):
40
+ raise ValueError("Front end worker not found in environment variable NAT_FRONT_END_WORKER.")
41
+
42
+ # Try to import the front end worker class
43
+ try:
44
+ front_end_worker_class: type[FastApiFrontEndPluginWorkerBase] = import_class_from_string(
45
+ front_end_worker_full_name)
46
+
47
+ if (not issubclass(front_end_worker_class, FastApiFrontEndPluginWorkerBase)):
48
+ raise ValueError(
49
+ f"Front end worker {front_end_worker_full_name} is not a subclass of FastApiFrontEndPluginWorker.")
50
+
51
+ # Load the config
52
+ config = load_config(config_file_path)
53
+
54
+ # Create an instance of the front end worker class
55
+ front_end_worker = front_end_worker_class(config)
56
+
57
+ nat_app = front_end_worker.build_app()
58
+
59
+ return nat_app
60
+
61
+ except ImportError as e:
62
+ raise ValueError(f"Front end worker {front_end_worker_full_name} not found.") from e
63
+ except Exception as e:
64
+ raise ValueError(f"Error loading front end worker {front_end_worker_full_name}: {e}") from e