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,308 @@
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 re
19
+ import typing
20
+ from abc import abstractmethod
21
+ from typing import TypeVar
22
+
23
+ from nat.data_models.intermediate_step import IntermediateStep
24
+ from nat.data_models.intermediate_step import IntermediateStepState
25
+ from nat.data_models.intermediate_step import TraceMetadata
26
+ from nat.data_models.span import MimeTypes
27
+ from nat.data_models.span import Span
28
+ from nat.data_models.span import SpanAttributes
29
+ from nat.data_models.span import SpanContext
30
+ from nat.data_models.span import event_type_to_span_kind
31
+ from nat.observability.exporter.base_exporter import IsolatedAttribute
32
+ from nat.observability.exporter.processing_exporter import ProcessingExporter
33
+ from nat.observability.mixin.serialize_mixin import SerializeMixin
34
+ from nat.observability.utils.dict_utils import merge_dicts
35
+ from nat.observability.utils.time_utils import ns_timestamp
36
+ from nat.utils.type_utils import override
37
+
38
+ if typing.TYPE_CHECKING:
39
+ from nat.builder.context import ContextState
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ InputSpanT = TypeVar("InputSpanT")
44
+ OutputSpanT = TypeVar("OutputSpanT")
45
+
46
+
47
+ class SpanExporter(ProcessingExporter[InputSpanT, OutputSpanT], SerializeMixin):
48
+ """Abstract base class for span exporters with processing pipeline support.
49
+
50
+ This class specializes ProcessingExporter for span-based telemetry export. It converts
51
+ IntermediateStep events into Span objects and supports processing pipelines for
52
+ span transformation before export.
53
+
54
+ The generic types work as follows:
55
+ - InputSpanT: The type of spans that enter the processing pipeline (typically Span)
56
+ - OutputSpanT: The type of spans after processing through the pipeline (e.g., OtelSpan)
57
+
58
+ Key Features:
59
+ - Automatic span creation from IntermediateStep events
60
+ - Span lifecycle management (start/end event tracking)
61
+ - Processing pipeline support via ProcessingExporter
62
+ - Metadata and attribute handling
63
+ - Usage information tracking
64
+ - Automatic isolation of mutable state for concurrent execution using descriptors
65
+
66
+ Inheritance Hierarchy:
67
+ - BaseExporter: Core event subscription and lifecycle management + DescriptorIsolationMixin
68
+ - ProcessingExporter: Adds processor pipeline functionality
69
+ - SpanExporter: Specializes for span creation and export
70
+
71
+ Event Processing Flow:
72
+ 1. IntermediateStep (START) → Create Span → Add to tracking
73
+ 2. IntermediateStep (END) → Complete Span → Process through pipeline → Export
74
+
75
+ Parameters
76
+ ----------
77
+ context_state: `ContextState`, optional
78
+ The context state to use for the exporter. Defaults to None.
79
+ span_prefix: `str`, optional
80
+ The prefix name to use for span attributes. If `None` the value of the `NAT_SPAN_PREFIX` environment
81
+ variable is used. Defaults to `"nat"` if neither are defined.
82
+ """
83
+
84
+ # Use descriptors for automatic isolation of span-specific state
85
+ _outstanding_spans: IsolatedAttribute[dict] = IsolatedAttribute(dict)
86
+ _span_stack: IsolatedAttribute[dict] = IsolatedAttribute(dict)
87
+ _metadata_stack: IsolatedAttribute[dict] = IsolatedAttribute(dict)
88
+
89
+ def __init__(self, context_state: "ContextState | None" = None, span_prefix: str | None = None):
90
+ super().__init__(context_state=context_state)
91
+ if span_prefix is None:
92
+ span_prefix = os.getenv("NAT_SPAN_PREFIX", "nat").strip() or "nat"
93
+
94
+ self._span_prefix = span_prefix
95
+
96
+ @abstractmethod
97
+ async def export_processed(self, item: OutputSpanT) -> None:
98
+ """Export the processed span.
99
+
100
+ Args:
101
+ item (OutputSpanT): The processed span to export.
102
+ """
103
+ pass
104
+
105
+ @override
106
+ def export(self, event: IntermediateStep) -> None:
107
+ """The main logic that reacts to each IntermediateStep.
108
+
109
+ Args:
110
+ event (IntermediateStep): The event to process.
111
+ """
112
+ if not isinstance(event, IntermediateStep):
113
+ return
114
+
115
+ if (event.event_state == IntermediateStepState.START):
116
+ self._process_start_event(event)
117
+ elif (event.event_state == IntermediateStepState.END):
118
+ self._process_end_event(event)
119
+
120
+ def _process_start_event(self, event: IntermediateStep):
121
+ """Process the start event of an intermediate step.
122
+
123
+ Args:
124
+ event (IntermediateStep): The event to process.
125
+ """
126
+
127
+ parent_span = None
128
+ span_ctx = None
129
+ workflow_trace_id = self._context_state.workflow_trace_id.get()
130
+
131
+ # Look up the parent span to establish hierarchy
132
+ # event.parent_id is the UUID of the last START step with a different UUID from current step
133
+ # This maintains proper parent-child relationships in the span tree
134
+ # Skip lookup if parent_id is "root" (indicates this is a top-level span)
135
+ if len(self._span_stack) > 0 and event.parent_id and event.parent_id != "root":
136
+
137
+ parent_span = self._span_stack.get(event.parent_id, None)
138
+ if parent_span is None:
139
+ logger.warning("No parent span found for step %s", event.UUID)
140
+ return
141
+
142
+ parent_span = parent_span.model_copy() if isinstance(parent_span, Span) else None
143
+ if parent_span and parent_span.context:
144
+ span_ctx = SpanContext(trace_id=parent_span.context.trace_id)
145
+ # No parent: adopt workflow trace id if available to keep all spans in the same trace
146
+ if span_ctx is None and workflow_trace_id:
147
+ span_ctx = SpanContext(trace_id=workflow_trace_id)
148
+
149
+ # Extract start/end times from the step
150
+ # By convention, `span_event_timestamp` is the time we started, `event_timestamp` is the time we ended.
151
+ # If span_event_timestamp is missing, we default to event_timestamp (meaning zero-length).
152
+ s_ts = event.payload.span_event_timestamp or event.payload.event_timestamp
153
+ start_ns = ns_timestamp(s_ts)
154
+
155
+ # Optional: embed the LLM/tool name if present
156
+ if event.payload.name:
157
+ sub_span_name = f"{event.payload.name}"
158
+ else:
159
+ sub_span_name = f"{event.payload.event_type}"
160
+
161
+ # Prefer parent/context trace id for attribute, else workflow trace id
162
+ _attr_trace_id = None
163
+ if span_ctx is not None:
164
+ _attr_trace_id = span_ctx.trace_id
165
+ elif parent_span and parent_span.context:
166
+ _attr_trace_id = parent_span.context.trace_id
167
+ elif workflow_trace_id:
168
+ _attr_trace_id = workflow_trace_id
169
+
170
+ attributes = {
171
+ f"{self._span_prefix}.event_type":
172
+ event.payload.event_type.value,
173
+ f"{self._span_prefix}.function.id":
174
+ event.function_ancestry.function_id if event.function_ancestry else "unknown",
175
+ f"{self._span_prefix}.function.name":
176
+ event.function_ancestry.function_name if event.function_ancestry else "unknown",
177
+ f"{self._span_prefix}.subspan.name":
178
+ event.payload.name or "",
179
+ f"{self._span_prefix}.event_timestamp":
180
+ event.event_timestamp,
181
+ f"{self._span_prefix}.framework":
182
+ event.payload.framework.value if event.payload.framework else "unknown",
183
+ f"{self._span_prefix}.conversation.id":
184
+ self._context_state.conversation_id.get() or "unknown",
185
+ f"{self._span_prefix}.workflow.run_id":
186
+ self._context_state.workflow_run_id.get() or "unknown",
187
+ f"{self._span_prefix}.workflow.trace_id": (f"{_attr_trace_id:032x}" if _attr_trace_id else "unknown"),
188
+ }
189
+
190
+ sub_span = Span(name=sub_span_name,
191
+ parent=parent_span,
192
+ context=span_ctx,
193
+ attributes=attributes,
194
+ start_time=start_ns)
195
+
196
+ span_kind = event_type_to_span_kind(event.event_type)
197
+ sub_span.set_attribute(f"{self._span_prefix}.span.kind", span_kind.value)
198
+
199
+ if event.payload.data and event.payload.data.input:
200
+ match = re.search(r"Human:\s*Question:\s*(.*)", str(event.payload.data.input))
201
+ if match:
202
+ human_question = match.group(1).strip()
203
+ sub_span.set_attribute(SpanAttributes.INPUT_VALUE.value, human_question)
204
+ else:
205
+ serialized_input, is_json = self._serialize_payload(event.payload.data.input)
206
+ sub_span.set_attribute(SpanAttributes.INPUT_VALUE.value, serialized_input)
207
+ sub_span.set_attribute(SpanAttributes.INPUT_MIME_TYPE.value,
208
+ MimeTypes.JSON.value if is_json else MimeTypes.TEXT.value)
209
+
210
+ # Add metadata to the metadata stack
211
+ start_metadata = event.payload.metadata or {}
212
+
213
+ if isinstance(start_metadata, dict):
214
+ self._metadata_stack[event.UUID] = start_metadata # type: ignore
215
+ elif isinstance(start_metadata, TraceMetadata):
216
+ self._metadata_stack[event.UUID] = start_metadata.model_dump() # type: ignore
217
+ else:
218
+ logger.warning("Invalid metadata type for step %s", event.UUID)
219
+ return
220
+
221
+ self._span_stack[event.UUID] = sub_span # type: ignore
222
+ self._outstanding_spans[event.UUID] = sub_span # type: ignore
223
+
224
+ logger.debug(
225
+ "Added span to tracking (outstanding: %d, stack: %d, event_id: %s)",
226
+ len(self._outstanding_spans), # type: ignore
227
+ len(self._span_stack), # type: ignore
228
+ event.UUID)
229
+
230
+ def _process_end_event(self, event: IntermediateStep):
231
+ """Process the end event of an intermediate step.
232
+
233
+ Args:
234
+ event (IntermediateStep): The event to process.
235
+ """
236
+
237
+ # Find the subspan that was created in the start event
238
+ sub_span: Span | None = self._outstanding_spans.pop(event.UUID, None) # type: ignore
239
+
240
+ if sub_span is None:
241
+ logger.warning("No subspan found for step %s", event.UUID)
242
+ return
243
+
244
+ self._span_stack.pop(event.UUID, None) # type: ignore
245
+
246
+ # Optionally add more attributes from usage_info or data
247
+ usage_info = event.payload.usage_info
248
+ if usage_info:
249
+ sub_span.set_attribute(SpanAttributes.NAT_USAGE_NUM_LLM_CALLS.value,
250
+ usage_info.num_llm_calls if usage_info.num_llm_calls else 0)
251
+ sub_span.set_attribute(SpanAttributes.NAT_USAGE_SECONDS_BETWEEN_CALLS.value,
252
+ usage_info.seconds_between_calls if usage_info.seconds_between_calls else 0)
253
+ sub_span.set_attribute(SpanAttributes.LLM_TOKEN_COUNT_PROMPT.value,
254
+ usage_info.token_usage.prompt_tokens if usage_info.token_usage else 0)
255
+ sub_span.set_attribute(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION.value,
256
+ usage_info.token_usage.completion_tokens if usage_info.token_usage else 0)
257
+ sub_span.set_attribute(SpanAttributes.LLM_TOKEN_COUNT_TOTAL.value,
258
+ usage_info.token_usage.total_tokens if usage_info.token_usage else 0)
259
+
260
+ if event.payload.data and event.payload.data.output is not None:
261
+ serialized_output, is_json = self._serialize_payload(event.payload.data.output)
262
+ sub_span.set_attribute(SpanAttributes.OUTPUT_VALUE.value, serialized_output)
263
+ sub_span.set_attribute(SpanAttributes.OUTPUT_MIME_TYPE.value,
264
+ MimeTypes.JSON.value if is_json else MimeTypes.TEXT.value)
265
+
266
+ # Merge metadata from start event with end event metadata
267
+ start_metadata = self._metadata_stack.pop(event.UUID) # type: ignore
268
+
269
+ if start_metadata is None:
270
+ logger.warning("No metadata found for step %s", event.UUID)
271
+ return
272
+
273
+ end_metadata = event.payload.metadata or {}
274
+
275
+ if not isinstance(end_metadata, dict | TraceMetadata):
276
+ logger.warning("Invalid metadata type for step %s", event.UUID)
277
+ return
278
+
279
+ if isinstance(end_metadata, TraceMetadata):
280
+ end_metadata = end_metadata.model_dump()
281
+
282
+ merged_metadata = merge_dicts(start_metadata, end_metadata)
283
+ serialized_metadata, is_json = self._serialize_payload(merged_metadata)
284
+ sub_span.set_attribute(f"{self._span_prefix}.metadata", serialized_metadata)
285
+ sub_span.set_attribute(f"{self._span_prefix}.metadata.mime_type",
286
+ MimeTypes.JSON.value if is_json else MimeTypes.TEXT.value)
287
+
288
+ end_ns = ns_timestamp(event.payload.event_timestamp)
289
+
290
+ # End the subspan
291
+ sub_span.end(end_time=end_ns)
292
+
293
+ # Export the span with processing pipeline
294
+ self._create_export_task(self._export_with_processing(sub_span)) # type: ignore
295
+
296
+ @override
297
+ async def _cleanup(self):
298
+ """Clean up any remaining spans."""
299
+ if self._outstanding_spans: # type: ignore
300
+ logger.warning("Not all spans were closed. Remaining: %s", self._outstanding_spans) # type: ignore
301
+
302
+ for span_info in self._outstanding_spans.values(): # type: ignore
303
+ span_info.end()
304
+
305
+ self._outstanding_spans.clear() # type: ignore
306
+ self._span_stack.clear() # type: ignore
307
+ self._metadata_stack.clear() # type: ignore
308
+ await super()._cleanup()
@@ -0,0 +1,335 @@
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 contextlib import asynccontextmanager
19
+
20
+ from nat.builder.context import ContextState
21
+ from nat.observability.exporter.base_exporter import BaseExporter
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class ExporterManager:
27
+ """
28
+ Manages the lifecycle of asynchronous exporters.
29
+
30
+ ExporterManager maintains a registry of exporters, allowing for dynamic addition and removal. It provides
31
+ methods to start and stop all registered exporters concurrently, ensuring proper synchronization and
32
+ lifecycle management. The manager is designed to prevent race conditions during exporter operations and to
33
+ handle exporter tasks in an asyncio event loop.
34
+
35
+ Each workflow execution gets its own ExporterManager instance to manage the lifecycle of exporters
36
+ during that workflow's execution.
37
+
38
+ Exporters added after `start()` is called will not be started automatically. They will only be
39
+ started on the next lifecycle (i.e., after a stop and subsequent start).
40
+
41
+ Args:
42
+ shutdown_timeout (int, optional): Maximum time in seconds to wait for exporters to shut down gracefully.
43
+ Defaults to 120 seconds.
44
+ """
45
+
46
+ def __init__(self, shutdown_timeout: int = 120):
47
+ """Initialize the ExporterManager."""
48
+ self._tasks: dict[str, asyncio.Task] = {}
49
+ self._running: bool = False
50
+ self._exporter_registry: dict[str, BaseExporter] = {}
51
+ self._is_registry_shared: bool = False
52
+ self._lock: asyncio.Lock = asyncio.Lock()
53
+ self._shutdown_event: asyncio.Event = asyncio.Event()
54
+ self._shutdown_timeout: int = shutdown_timeout
55
+ # Track isolated exporters for proper cleanup
56
+ self._active_isolated_exporters: dict[str, BaseExporter] = {}
57
+
58
+ @classmethod
59
+ def _create_with_shared_registry(cls, shutdown_timeout: int,
60
+ shared_registry: dict[str, BaseExporter]) -> "ExporterManager":
61
+ """Internal factory method for creating instances with shared registry."""
62
+ instance = cls.__new__(cls)
63
+ instance._tasks = {}
64
+ instance._running = False
65
+ instance._exporter_registry = shared_registry
66
+ instance._is_registry_shared = True
67
+ instance._lock = asyncio.Lock()
68
+ instance._shutdown_event = asyncio.Event()
69
+ instance._shutdown_timeout = shutdown_timeout
70
+ instance._active_isolated_exporters = {}
71
+ return instance
72
+
73
+ def _ensure_registry_owned(self):
74
+ """Ensure we own the registry (copy-on-write)."""
75
+ if self._is_registry_shared:
76
+ self._exporter_registry = self._exporter_registry.copy()
77
+ self._is_registry_shared = False
78
+
79
+ def add_exporter(self, name: str, exporter: BaseExporter) -> None:
80
+ """
81
+ Add an exporter to the manager.
82
+
83
+ Args:
84
+ name (str): The unique name for the exporter.
85
+ exporter (BaseExporter): The exporter instance to add.
86
+ """
87
+ self._ensure_registry_owned()
88
+
89
+ if name in self._exporter_registry:
90
+ logger.warning("Exporter '%s' already registered. Overwriting.", name)
91
+
92
+ self._exporter_registry[name] = exporter
93
+
94
+ def remove_exporter(self, name: str) -> None:
95
+ """
96
+ Remove an exporter from the manager.
97
+
98
+ Args:
99
+ name (str): The name of the exporter to remove.
100
+ """
101
+ self._ensure_registry_owned()
102
+ if name in self._exporter_registry:
103
+ del self._exporter_registry[name]
104
+ else:
105
+ raise ValueError(f"Cannot remove exporter '{name}' because it is not registered.")
106
+
107
+ def get_exporter(self, name: str) -> BaseExporter:
108
+ """
109
+ Get an exporter instance by name.
110
+
111
+ Args:
112
+ name (str): The name of the exporter to retrieve.
113
+
114
+ Returns:
115
+ BaseExporter: The exporter instance if found, otherwise raises a ValueError.
116
+
117
+ Raises:
118
+ ValueError: If the exporter is not found.
119
+ """
120
+ exporter = self._exporter_registry.get(name, None)
121
+
122
+ if exporter is not None:
123
+ return exporter
124
+
125
+ raise ValueError(f"Cannot get exporter '{name}' because it is not registered.")
126
+
127
+ async def get_all_exporters(self) -> dict[str, BaseExporter]:
128
+ """
129
+ Get all registered exporters instances.
130
+
131
+ Returns:
132
+ dict[str, BaseExporter]: A dictionary mapping exporter names to exporter instances.
133
+ """
134
+ return self._exporter_registry
135
+
136
+ def create_isolated_exporters(self, context_state: ContextState | None = None) -> dict[str, BaseExporter]:
137
+ """
138
+ Create isolated copies of all exporters for concurrent execution.
139
+
140
+ This uses copy-on-write to efficiently create isolated instances that share
141
+ expensive resources but have separate mutable state.
142
+
143
+ Args:
144
+ context_state (ContextState | None, optional): The isolated context state for the new exporter instances.
145
+ If not provided, a new context state will be created.
146
+
147
+ Returns:
148
+ dict[str, BaseExporter]: Dictionary of isolated exporter instances
149
+ """
150
+ # Provide default context state if None
151
+ if context_state is None:
152
+ context_state = ContextState.get()
153
+
154
+ isolated_exporters = {}
155
+ for name, exporter in self._exporter_registry.items():
156
+ if hasattr(exporter, 'create_isolated_instance'):
157
+ isolated_exporters[name] = exporter.create_isolated_instance(context_state)
158
+ else:
159
+ # Fallback for exporters that don't support isolation
160
+ logger.warning("Exporter '%s' doesn't support isolation, using shared instance", name)
161
+ isolated_exporters[name] = exporter
162
+ return isolated_exporters
163
+
164
+ async def _cleanup_isolated_exporters(self):
165
+ """Explicitly clean up isolated exporter instances."""
166
+ if not self._active_isolated_exporters:
167
+ return
168
+
169
+ logger.debug("Cleaning up %d isolated exporters", len(self._active_isolated_exporters))
170
+
171
+ cleanup_tasks = []
172
+ for name, exporter in self._active_isolated_exporters.items():
173
+ try:
174
+ # Only clean up isolated instances that have a stop method
175
+ if hasattr(exporter, 'stop') and exporter.is_isolated_instance:
176
+ cleanup_tasks.append(self._cleanup_single_exporter(name, exporter))
177
+ else:
178
+ logger.debug("Skipping cleanup for non-isolated exporter '%s'", name)
179
+ except Exception as e:
180
+ logger.exception("Error preparing cleanup for isolated exporter '%s': %s", name, e)
181
+
182
+ if cleanup_tasks:
183
+ # Run cleanup tasks concurrently with timeout
184
+ try:
185
+ await asyncio.wait_for(asyncio.gather(*cleanup_tasks, return_exceptions=True),
186
+ timeout=self._shutdown_timeout)
187
+ except TimeoutError:
188
+ logger.warning("Some isolated exporters did not clean up within timeout")
189
+
190
+ self._active_isolated_exporters.clear()
191
+
192
+ async def _cleanup_single_exporter(self, name: str, exporter: BaseExporter):
193
+ """Clean up a single isolated exporter."""
194
+ try:
195
+ logger.debug("Stopping isolated exporter '%s'", name)
196
+ await exporter.stop()
197
+ except Exception as e:
198
+ logger.exception("Error stopping isolated exporter '%s': %s", name, e)
199
+
200
+ @asynccontextmanager
201
+ async def start(self, context_state: ContextState | None = None):
202
+ """
203
+ Start all registered exporters concurrently.
204
+
205
+ This method acquires a lock to ensure only one start/stop cycle is active at a time. It starts all
206
+ currently registered exporters in their own asyncio tasks. Exporters added after this call will not be
207
+ started until the next lifecycle.
208
+
209
+ Args:
210
+ context_state: Optional context state for creating isolated exporters
211
+
212
+ Yields:
213
+ ExporterManager: The manager instance for use within the context.
214
+
215
+ Raises:
216
+ RuntimeError: If the manager is already running.
217
+ """
218
+ async with self._lock:
219
+ if self._running:
220
+ raise RuntimeError("Exporter manager is already running")
221
+ self._shutdown_event.clear()
222
+ self._running = True
223
+
224
+ # Create isolated exporters if context_state provided, otherwise use originals
225
+ if context_state:
226
+ exporters_to_start = self.create_isolated_exporters(context_state)
227
+ # Store isolated exporters for cleanup
228
+ self._active_isolated_exporters = exporters_to_start
229
+ logger.debug("Created %d isolated exporters", len(exporters_to_start))
230
+ else:
231
+ exporters_to_start = self._exporter_registry
232
+ # Clear isolated exporters since we're using originals
233
+ self._active_isolated_exporters = {}
234
+
235
+ # Start all exporters concurrently
236
+ exporters = []
237
+ tasks = []
238
+ for name, exporter in exporters_to_start.items():
239
+ task = asyncio.create_task(self._run_exporter(name, exporter))
240
+ exporters.append(exporter)
241
+ self._tasks[name] = task
242
+ tasks.append(task)
243
+
244
+ # Wait for all exporters to be ready
245
+ await asyncio.gather(*[exporter.wait_ready() for exporter in exporters])
246
+
247
+ try:
248
+ yield self
249
+ finally:
250
+ # Clean up isolated exporters BEFORE stopping tasks
251
+ try:
252
+ await self._cleanup_isolated_exporters()
253
+ except Exception as e:
254
+ logger.exception("Error during isolated exporter cleanup: %s", e)
255
+
256
+ # Then stop the manager tasks
257
+ await self.stop()
258
+
259
+ async def _run_exporter(self, name: str, exporter: BaseExporter):
260
+ """
261
+ Run an exporter in its own task.
262
+
263
+ Args:
264
+ name (str): The name of the exporter.
265
+ exporter (BaseExporter): The exporter instance to run.
266
+ """
267
+ try:
268
+ async with exporter.start():
269
+ logger.info("Started exporter '%s'", name)
270
+ # The context manager will keep the task alive until shutdown is signaled
271
+ await self._shutdown_event.wait()
272
+ logger.info("Stopped exporter '%s'", name)
273
+ except asyncio.CancelledError:
274
+ logger.debug("Exporter '%s' task cancelled", name)
275
+ logger.info("Stopped exporter '%s'", name)
276
+ raise
277
+ except Exception as e:
278
+ logger.error("Failed to run exporter '%s': %s", name, str(e))
279
+ # Re-raise the exception to ensure it's properly handled
280
+ raise
281
+
282
+ async def stop(self) -> None:
283
+ """
284
+ Stop all registered exporters.
285
+
286
+ This method signals all running exporter tasks to shut down and waits for their completion, up to the
287
+ configured shutdown timeout. If any tasks do not complete in time, a warning is logged.
288
+ """
289
+ async with self._lock:
290
+ if not self._running:
291
+ return
292
+ self._running = False
293
+ self._shutdown_event.set()
294
+
295
+ # Create a copy of tasks to prevent modification during iteration
296
+ tasks_to_cancel = dict(self._tasks)
297
+ self._tasks.clear()
298
+ stuck_tasks = []
299
+ # Cancel all running tasks and await their completion
300
+ for name, task in tasks_to_cancel.items():
301
+ try:
302
+ task.cancel()
303
+ await asyncio.wait_for(task, timeout=self._shutdown_timeout)
304
+ except TimeoutError:
305
+ logger.warning("Exporter '%s' task did not shut down in time and may be stuck.", name)
306
+ stuck_tasks.append(name)
307
+ except asyncio.CancelledError:
308
+ logger.debug("Exporter '%s' task cancelled", name)
309
+ except Exception as e:
310
+ logger.exception("Failed to stop exporter '%s': %s", name, str(e))
311
+
312
+ if stuck_tasks:
313
+ logger.warning("Exporters did not shut down in time: %s", ", ".join(stuck_tasks))
314
+
315
+ @staticmethod
316
+ def from_exporters(exporters: dict[str, BaseExporter], shutdown_timeout: int = 120) -> "ExporterManager":
317
+ """
318
+ Create an ExporterManager from a dictionary of exporters.
319
+ """
320
+ exporter_manager = ExporterManager(shutdown_timeout=shutdown_timeout)
321
+ for name, exporter in exporters.items():
322
+ exporter_manager.add_exporter(name, exporter)
323
+
324
+ return exporter_manager
325
+
326
+ def get(self) -> "ExporterManager":
327
+ """
328
+ Create a copy of this ExporterManager with the same configuration using copy-on-write.
329
+
330
+ This is the most efficient approach - shares the registry until modifications are needed.
331
+
332
+ Returns:
333
+ ExporterManager: A new ExporterManager instance with shared exporters (copy-on-write).
334
+ """
335
+ return self._create_with_shared_registry(self._shutdown_timeout, self._exporter_registry)
@@ -0,0 +1,14 @@
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.
@@ -0,0 +1,26 @@
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
+ from pydantic import BaseModel
17
+ from pydantic import Field
18
+
19
+
20
+ class BatchConfigMixin(BaseModel):
21
+ """Mixin for telemetry exporters that require batching."""
22
+ batch_size: int = Field(default=100, description="The batch size for the telemetry exporter.")
23
+ flush_interval: float = Field(default=5.0, description="The flush interval for the telemetry exporter.")
24
+ max_queue_size: int = Field(default=1000, description="The maximum queue size for the telemetry exporter.")
25
+ drop_on_overflow: bool = Field(default=False, description="Whether to drop on overflow for the telemetry exporter.")
26
+ shutdown_timeout: float = Field(default=10.0, description="The shutdown timeout for the telemetry exporter.")