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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. aiq/__init__.py +2 -2
  2. nat/agent/base.py +24 -15
  3. nat/agent/dual_node.py +9 -4
  4. nat/agent/prompt_optimizer/prompt.py +68 -0
  5. nat/agent/prompt_optimizer/register.py +149 -0
  6. nat/agent/react_agent/agent.py +79 -47
  7. nat/agent/react_agent/register.py +41 -21
  8. nat/agent/reasoning_agent/reasoning_agent.py +11 -9
  9. nat/agent/register.py +1 -1
  10. nat/agent/rewoo_agent/agent.py +326 -148
  11. nat/agent/rewoo_agent/prompt.py +19 -22
  12. nat/agent/rewoo_agent/register.py +46 -26
  13. nat/agent/tool_calling_agent/agent.py +84 -28
  14. nat/agent/tool_calling_agent/register.py +51 -28
  15. nat/authentication/api_key/api_key_auth_provider.py +2 -2
  16. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  17. nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
  18. nat/authentication/interfaces.py +5 -2
  19. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +40 -20
  20. nat/authentication/oauth2/oauth2_resource_server_config.py +124 -0
  21. nat/authentication/register.py +0 -1
  22. nat/builder/builder.py +56 -24
  23. nat/builder/component_utils.py +9 -5
  24. nat/builder/context.py +46 -11
  25. nat/builder/eval_builder.py +16 -11
  26. nat/builder/framework_enum.py +1 -0
  27. nat/builder/front_end.py +1 -1
  28. nat/builder/function.py +378 -8
  29. nat/builder/function_base.py +3 -3
  30. nat/builder/function_info.py +6 -8
  31. nat/builder/user_interaction_manager.py +2 -2
  32. nat/builder/workflow.py +13 -1
  33. nat/builder/workflow_builder.py +281 -76
  34. nat/cli/cli_utils/config_override.py +2 -2
  35. nat/cli/commands/evaluate.py +1 -1
  36. nat/cli/commands/info/info.py +16 -6
  37. nat/cli/commands/info/list_channels.py +1 -1
  38. nat/cli/commands/info/list_components.py +7 -8
  39. nat/cli/commands/mcp/__init__.py +14 -0
  40. nat/cli/commands/mcp/mcp.py +986 -0
  41. nat/cli/commands/object_store/__init__.py +14 -0
  42. nat/cli/commands/object_store/object_store.py +227 -0
  43. nat/cli/commands/optimize.py +90 -0
  44. nat/cli/commands/registry/publish.py +2 -2
  45. nat/cli/commands/registry/pull.py +2 -2
  46. nat/cli/commands/registry/remove.py +2 -2
  47. nat/cli/commands/registry/search.py +15 -17
  48. nat/cli/commands/start.py +16 -5
  49. nat/cli/commands/uninstall.py +1 -1
  50. nat/cli/commands/workflow/templates/config.yml.j2 +0 -1
  51. nat/cli/commands/workflow/templates/pyproject.toml.j2 +4 -1
  52. nat/cli/commands/workflow/templates/register.py.j2 +0 -1
  53. nat/cli/commands/workflow/workflow_commands.py +9 -13
  54. nat/cli/entrypoint.py +8 -10
  55. nat/cli/register_workflow.py +38 -4
  56. nat/cli/type_registry.py +75 -6
  57. nat/control_flow/__init__.py +0 -0
  58. nat/control_flow/register.py +20 -0
  59. nat/control_flow/router_agent/__init__.py +0 -0
  60. nat/control_flow/router_agent/agent.py +329 -0
  61. nat/control_flow/router_agent/prompt.py +48 -0
  62. nat/control_flow/router_agent/register.py +91 -0
  63. nat/control_flow/sequential_executor.py +166 -0
  64. nat/data_models/agent.py +34 -0
  65. nat/data_models/api_server.py +10 -10
  66. nat/data_models/authentication.py +23 -9
  67. nat/data_models/common.py +1 -1
  68. nat/data_models/component.py +2 -0
  69. nat/data_models/component_ref.py +11 -0
  70. nat/data_models/config.py +41 -17
  71. nat/data_models/dataset_handler.py +1 -1
  72. nat/data_models/discovery_metadata.py +4 -4
  73. nat/data_models/evaluate.py +4 -1
  74. nat/data_models/function.py +34 -0
  75. nat/data_models/function_dependencies.py +14 -6
  76. nat/data_models/gated_field_mixin.py +242 -0
  77. nat/data_models/intermediate_step.py +3 -3
  78. nat/data_models/optimizable.py +119 -0
  79. nat/data_models/optimizer.py +149 -0
  80. nat/data_models/swe_bench_model.py +1 -1
  81. nat/data_models/temperature_mixin.py +44 -0
  82. nat/data_models/thinking_mixin.py +86 -0
  83. nat/data_models/top_p_mixin.py +44 -0
  84. nat/embedder/nim_embedder.py +1 -1
  85. nat/embedder/openai_embedder.py +1 -1
  86. nat/embedder/register.py +0 -1
  87. nat/eval/config.py +3 -1
  88. nat/eval/dataset_handler/dataset_handler.py +71 -7
  89. nat/eval/evaluate.py +86 -31
  90. nat/eval/evaluator/base_evaluator.py +1 -1
  91. nat/eval/evaluator/evaluator_model.py +13 -0
  92. nat/eval/intermediate_step_adapter.py +1 -1
  93. nat/eval/rag_evaluator/evaluate.py +2 -2
  94. nat/eval/rag_evaluator/register.py +3 -3
  95. nat/eval/register.py +4 -1
  96. nat/eval/remote_workflow.py +3 -3
  97. nat/eval/runtime_evaluator/__init__.py +14 -0
  98. nat/eval/runtime_evaluator/evaluate.py +123 -0
  99. nat/eval/runtime_evaluator/register.py +100 -0
  100. nat/eval/swe_bench_evaluator/evaluate.py +6 -6
  101. nat/eval/trajectory_evaluator/evaluate.py +1 -1
  102. nat/eval/trajectory_evaluator/register.py +1 -1
  103. nat/eval/tunable_rag_evaluator/evaluate.py +4 -7
  104. nat/eval/utils/eval_trace_ctx.py +89 -0
  105. nat/eval/utils/weave_eval.py +18 -9
  106. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  107. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  108. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +3 -3
  109. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
  110. nat/experimental/test_time_compute/models/strategy_base.py +5 -4
  111. nat/experimental/test_time_compute/register.py +0 -1
  112. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -3
  113. nat/front_ends/console/authentication_flow_handler.py +82 -30
  114. nat/front_ends/console/console_front_end_plugin.py +8 -5
  115. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  116. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  117. nat/front_ends/fastapi/fastapi_front_end_config.py +36 -5
  118. nat/front_ends/fastapi/fastapi_front_end_controller.py +4 -4
  119. nat/front_ends/fastapi/fastapi_front_end_plugin.py +135 -4
  120. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +481 -281
  121. nat/front_ends/fastapi/job_store.py +518 -99
  122. nat/front_ends/fastapi/main.py +11 -19
  123. nat/front_ends/fastapi/message_handler.py +13 -14
  124. nat/front_ends/fastapi/message_validator.py +17 -19
  125. nat/front_ends/fastapi/response_helpers.py +4 -4
  126. nat/front_ends/fastapi/step_adaptor.py +2 -2
  127. nat/front_ends/fastapi/utils.py +57 -0
  128. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  129. nat/front_ends/mcp/mcp_front_end_config.py +10 -1
  130. nat/front_ends/mcp/mcp_front_end_plugin.py +45 -13
  131. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +116 -8
  132. nat/front_ends/mcp/tool_converter.py +44 -14
  133. nat/front_ends/register.py +0 -1
  134. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  135. nat/llm/aws_bedrock_llm.py +24 -12
  136. nat/llm/azure_openai_llm.py +13 -6
  137. nat/llm/litellm_llm.py +69 -0
  138. nat/llm/nim_llm.py +20 -8
  139. nat/llm/openai_llm.py +14 -6
  140. nat/llm/register.py +4 -1
  141. nat/llm/utils/env_config_value.py +2 -3
  142. nat/llm/utils/thinking.py +215 -0
  143. nat/meta/pypi.md +9 -9
  144. nat/object_store/register.py +0 -1
  145. nat/observability/exporter/base_exporter.py +3 -3
  146. nat/observability/exporter/file_exporter.py +1 -1
  147. nat/observability/exporter/processing_exporter.py +309 -81
  148. nat/observability/exporter/span_exporter.py +1 -1
  149. nat/observability/exporter_manager.py +7 -7
  150. nat/observability/mixin/file_mixin.py +7 -7
  151. nat/observability/mixin/redaction_config_mixin.py +42 -0
  152. nat/observability/mixin/tagging_config_mixin.py +62 -0
  153. nat/observability/mixin/type_introspection_mixin.py +420 -107
  154. nat/observability/processor/batching_processor.py +5 -7
  155. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  156. nat/observability/processor/processor.py +3 -0
  157. nat/observability/processor/processor_factory.py +70 -0
  158. nat/observability/processor/redaction/__init__.py +24 -0
  159. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  160. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  161. nat/observability/processor/redaction/redaction_processor.py +177 -0
  162. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  163. nat/observability/processor/span_tagging_processor.py +68 -0
  164. nat/observability/register.py +6 -4
  165. nat/profiler/calc/calc_runner.py +3 -4
  166. nat/profiler/callbacks/agno_callback_handler.py +1 -1
  167. nat/profiler/callbacks/langchain_callback_handler.py +6 -6
  168. nat/profiler/callbacks/llama_index_callback_handler.py +3 -3
  169. nat/profiler/callbacks/semantic_kernel_callback_handler.py +3 -3
  170. nat/profiler/data_frame_row.py +1 -1
  171. nat/profiler/decorators/framework_wrapper.py +62 -13
  172. nat/profiler/decorators/function_tracking.py +160 -3
  173. nat/profiler/forecasting/models/forecasting_base_model.py +3 -1
  174. nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +1 -1
  175. nat/profiler/inference_optimization/data_models.py +3 -3
  176. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +7 -8
  177. nat/profiler/inference_optimization/token_uniqueness.py +1 -1
  178. nat/profiler/parameter_optimization/__init__.py +0 -0
  179. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  180. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  181. nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
  182. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  183. nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
  184. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  185. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  186. nat/profiler/profile_runner.py +14 -9
  187. nat/profiler/utils.py +4 -2
  188. nat/registry_handlers/local/local_handler.py +2 -2
  189. nat/registry_handlers/package_utils.py +1 -2
  190. nat/registry_handlers/pypi/pypi_handler.py +23 -26
  191. nat/registry_handlers/register.py +3 -4
  192. nat/registry_handlers/rest/rest_handler.py +12 -13
  193. nat/retriever/milvus/retriever.py +2 -2
  194. nat/retriever/nemo_retriever/retriever.py +1 -1
  195. nat/retriever/register.py +0 -1
  196. nat/runtime/loader.py +2 -2
  197. nat/runtime/runner.py +3 -2
  198. nat/runtime/session.py +43 -8
  199. nat/settings/global_settings.py +16 -5
  200. nat/tool/chat_completion.py +5 -2
  201. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +3 -3
  202. nat/tool/datetime_tools.py +49 -9
  203. nat/tool/document_search.py +2 -2
  204. nat/tool/github_tools.py +450 -0
  205. nat/tool/nvidia_rag.py +1 -1
  206. nat/tool/register.py +2 -9
  207. nat/tool/retriever.py +3 -2
  208. nat/utils/callable_utils.py +70 -0
  209. nat/utils/data_models/schema_validator.py +3 -3
  210. nat/utils/exception_handlers/automatic_retries.py +104 -51
  211. nat/utils/exception_handlers/schemas.py +1 -1
  212. nat/utils/io/yaml_tools.py +2 -2
  213. nat/utils/log_levels.py +25 -0
  214. nat/utils/reactive/base/observable_base.py +2 -2
  215. nat/utils/reactive/base/observer_base.py +1 -1
  216. nat/utils/reactive/observable.py +2 -2
  217. nat/utils/reactive/observer.py +4 -4
  218. nat/utils/reactive/subscription.py +1 -1
  219. nat/utils/settings/global_settings.py +6 -8
  220. nat/utils/type_converter.py +4 -3
  221. nat/utils/type_utils.py +9 -5
  222. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/METADATA +42 -16
  223. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/RECORD +230 -189
  224. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/entry_points.txt +1 -0
  225. nat/cli/commands/info/list_mcp.py +0 -304
  226. nat/tool/github_tools/create_github_commit.py +0 -133
  227. nat/tool/github_tools/create_github_issue.py +0 -87
  228. nat/tool/github_tools/create_github_pr.py +0 -106
  229. nat/tool/github_tools/get_github_file.py +0 -106
  230. nat/tool/github_tools/get_github_issue.py +0 -166
  231. nat/tool/github_tools/get_github_pr.py +0 -256
  232. nat/tool/github_tools/update_github_issue.py +0 -100
  233. nat/tool/mcp/exceptions.py +0 -142
  234. nat/tool/mcp/mcp_client.py +0 -255
  235. nat/tool/mcp/mcp_tool.py +0 -96
  236. nat/utils/exception_handlers/mcp.py +0 -211
  237. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  238. /nat/{tool/mcp → authentication/credential_validator}/__init__.py +0 -0
  239. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/WHEEL +0 -0
  240. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  241. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/licenses/LICENSE.md +0 -0
  242. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,55 @@
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 logging
17
+ from typing import TypeVar
18
+
19
+ from nat.observability.processor.processor import Processor
20
+ from nat.utils.type_utils import override
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ FalsyT = TypeVar("FalsyT")
25
+
26
+
27
+ class FalsyBatchFilterProcessor(Processor[list[FalsyT], list[FalsyT]]):
28
+ """Processor that filters out falsy items from a batch."""
29
+
30
+ @override
31
+ async def process(self, item: list[FalsyT]) -> list[FalsyT]:
32
+ """Filter out falsy items from a batch.
33
+
34
+ Args:
35
+ item (list[FalsyT]): The batch of items to filter.
36
+
37
+ Returns:
38
+ list[FalsyT]: The filtered batch.
39
+ """
40
+ return [i for i in item if i]
41
+
42
+
43
+ class DictBatchFilterProcessor(FalsyBatchFilterProcessor[dict]):
44
+ """Processor that filters out empty dict items from a batch."""
45
+ pass
46
+
47
+
48
+ class ListBatchFilterProcessor(FalsyBatchFilterProcessor[list]):
49
+ """Processor that filters out empty list items from a batch."""
50
+ pass
51
+
52
+
53
+ class SetBatchFilterProcessor(FalsyBatchFilterProcessor[set]):
54
+ """Processor that filters out empty set items from a batch."""
55
+ pass
@@ -58,6 +58,9 @@ class Processor(Generic[InputT, OutputT], TypeIntrospectionMixin, ABC):
58
58
  chained processors.
59
59
  """
60
60
 
61
+ # All processors automatically use this for signature checking
62
+ _signature_method = 'process'
63
+
61
64
  @abstractmethod
62
65
  async def process(self, item: InputT) -> OutputT:
63
66
  """Process an item and return a potentially different type.
@@ -0,0 +1,70 @@
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
+ from typing import Any
17
+
18
+ from nat.observability.processor.processor import Processor
19
+
20
+
21
+ def processor_factory(processor_class: type, from_type: type[Any], to_type: type[Any]) -> type[Processor]:
22
+ """Create a concrete processor class from a processor class and types.
23
+
24
+ Args:
25
+ processor_class (type): The processor class to create a concrete instance of
26
+ from_type (type[Any]): The type of the input data
27
+ to_type (type[Any]): The type of the output data
28
+
29
+ Returns:
30
+ type[Processor]: The concrete processor class
31
+ """
32
+
33
+ class ConcreteProcessor(processor_class[from_type, to_type]): # type: ignore
34
+ pass
35
+
36
+ return ConcreteProcessor
37
+
38
+
39
+ def processor_factory_from_type(processor_class: type, from_type: type[Any]) -> type[Processor]:
40
+ """Create a concrete processor class from a processor class and input type.
41
+
42
+ Args:
43
+ processor_class (type): The processor class to create a concrete instance of
44
+ from_type (type[Any]): The type of the input data
45
+
46
+ Returns:
47
+ type[Processor]: The concrete processor class
48
+ """
49
+
50
+ class ConcreteProcessor(processor_class[from_type]): # type: ignore
51
+ pass
52
+
53
+ return ConcreteProcessor
54
+
55
+
56
+ def processor_factory_to_type(processor_class: type, to_type: type[Any]) -> type[Processor]:
57
+ """Create a concrete processor class from a processor class and output type.
58
+
59
+ Args:
60
+ processor_class (type): The processor class to create a concrete instance of
61
+ to_type (type[Any]): The type of the output data
62
+
63
+ Returns:
64
+ type[Processor]: The concrete processor class
65
+ """
66
+
67
+ class ConcreteProcessor(processor_class[to_type]): # type: ignore
68
+ pass
69
+
70
+ return ConcreteProcessor
@@ -0,0 +1,24 @@
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
+ from .redaction_processor import RedactionContext
17
+ from .redaction_processor import RedactionContextState
18
+ from .span_header_redaction_processor import SpanHeaderRedactionProcessor
19
+
20
+ __all__ = [
21
+ "SpanHeaderRedactionProcessor",
22
+ "RedactionContext",
23
+ "RedactionContextState",
24
+ ]
@@ -0,0 +1,125 @@
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 logging
17
+ from abc import abstractmethod
18
+ from collections.abc import Callable
19
+ from typing import Any
20
+ from typing import TypeVar
21
+
22
+ from nat.observability.processor.redaction.redaction_processor import RedactionContext
23
+ from nat.observability.processor.redaction.redaction_processor import RedactionContextState
24
+ from nat.observability.processor.redaction.redaction_processor import RedactionInputT
25
+ from nat.observability.processor.redaction.redaction_processor import RedactionProcessor
26
+ from nat.utils.type_utils import override
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Type variable for the data type extracted from context
31
+ RedactionDataT = TypeVar('RedactionDataT')
32
+
33
+
34
+ class ContextualRedactionProcessor(RedactionProcessor[RedactionInputT, RedactionDataT]):
35
+ """Generic processor with context-aware caching for any data type.
36
+
37
+ Provides a framework for redaction processors that need to:
38
+ - Extract data from the request context (headers, cookies, query params, etc.)
39
+ - Execute callbacks to determine redaction decisions
40
+ - Cache results within the request context to avoid redundant callback executions
41
+ - Handle race conditions with atomic operations
42
+
43
+ This class handles all the generic caching, context management, and callback
44
+ execution logic. Subclasses only need to implement data extraction and validation.
45
+
46
+ Args:
47
+ callback: Callable that determines if redaction should occur based on extracted data
48
+ enabled: Whether the processor is enabled
49
+ force_redact: If True, always redact regardless of data checks
50
+ redaction_value: The value to replace redacted attributes with
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ callback: Callable[..., Any],
56
+ enabled: bool,
57
+ force_redact: bool,
58
+ redaction_value: str,
59
+ ):
60
+ self.callback = callback
61
+ self.enabled = enabled
62
+ self.force_redact = force_redact
63
+ self.redaction_value = redaction_value
64
+ self._redaction_context = RedactionContext(RedactionContextState())
65
+
66
+ @abstractmethod
67
+ def extract_data_from_context(self) -> RedactionDataT | None:
68
+ """Extract the relevant data from the context for redaction decision.
69
+
70
+ This method must be implemented by subclasses to extract their specific
71
+ data type (headers, cookies, query params, etc.) from the request context
72
+
73
+ Returns:
74
+ RedactionDataT | None: The extracted data, or None if no relevant data found
75
+ """
76
+ pass
77
+
78
+ @abstractmethod
79
+ def validate_data(self, data: RedactionDataT) -> bool:
80
+ """Validate that the extracted data is suitable for callback execution.
81
+
82
+ This method allows subclasses to implement their own validation logic
83
+ (e.g., checking if headers exist, if cookies are not empty, etc.).
84
+
85
+ Args:
86
+ data (RedactionDataT): The extracted data to validate
87
+
88
+ Returns:
89
+ bool: True if the data is valid for callback execution, False otherwise
90
+ """
91
+ pass
92
+
93
+ @override
94
+ async def should_redact(self, item: RedactionInputT) -> bool:
95
+ """Determine if this span should be redacted based on extracted data.
96
+
97
+ Extracts the relevant data from the context, validates it, and passes it to the
98
+ callback function to determine if redaction should occur. Results are cached
99
+ within the request context to avoid redundant callback executions.
100
+
101
+ Args:
102
+ item (RedactionInputT): The item to check
103
+
104
+ Returns:
105
+ bool: True if the span should be redacted, False otherwise
106
+ """
107
+ # If force_redact is enabled, always redact regardless of other conditions
108
+ if self.force_redact:
109
+ return True
110
+
111
+ if not self.enabled:
112
+ return False
113
+
114
+ # Extract data using subclass implementation
115
+ data = self.extract_data_from_context()
116
+ if data is None:
117
+ return False
118
+
119
+ # Validate data using subclass implementation
120
+ if not self.validate_data(data):
121
+ return False
122
+
123
+ # Use the generic caching framework for callback execution
124
+ async with self._redaction_context.redaction_manager() as manager:
125
+ return await manager.redaction_check(self.callback, data)
@@ -0,0 +1,66 @@
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
+ from collections.abc import Callable
17
+ from typing import Any
18
+
19
+ from nat.data_models.span import Span
20
+ from nat.observability.processor.redaction.contextual_redaction_processor import ContextualRedactionProcessor
21
+ from nat.observability.processor.redaction.redaction_processor import RedactionDataT
22
+ from nat.utils.type_utils import override
23
+
24
+
25
+ class ContextualSpanRedactionProcessor(ContextualRedactionProcessor[Span, RedactionDataT]):
26
+ """Processor that redacts the Span based on the Span attributes.
27
+
28
+ Args:
29
+ attributes: List of span attribute keys to redact
30
+ callback: Callable that determines if redaction should occur
31
+ enabled: Whether the processor is enabled
32
+ force_redact: If True, always redact regardless of callback
33
+ redaction_value: The value to replace redacted attributes with
34
+ """
35
+
36
+ def __init__(self,
37
+ attributes: list[str],
38
+ callback: Callable[..., Any],
39
+ enabled: bool,
40
+ force_redact: bool,
41
+ redaction_value: str,
42
+ redaction_tag: str | None = None):
43
+ super().__init__(callback=callback, enabled=enabled, force_redact=force_redact, redaction_value=redaction_value)
44
+ self.attributes = attributes
45
+ self.redaction_tag = redaction_tag
46
+
47
+ @override
48
+ async def redact_item(self, item: Span) -> Span:
49
+ """Redact specified attributes in the span.
50
+
51
+ Replaces the values of configured attributes with the redaction value.
52
+
53
+ Args:
54
+ item (Span): The span to redact
55
+
56
+ Returns:
57
+ Span: The span with redacted attributes
58
+ """
59
+ for key in self.attributes:
60
+ if key in item.attributes:
61
+ item.set_attribute(key, self.redaction_value)
62
+
63
+ if self.redaction_tag:
64
+ item.set_attribute(self.redaction_tag, True)
65
+
66
+ return item
@@ -0,0 +1,177 @@
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 logging
17
+ from abc import abstractmethod
18
+ from collections.abc import AsyncGenerator
19
+ from collections.abc import Callable
20
+ from contextlib import asynccontextmanager
21
+ from contextvars import ContextVar
22
+ from dataclasses import dataclass
23
+ from dataclasses import field
24
+ from typing import Any
25
+ from typing import Generic
26
+ from typing import TypeVar
27
+
28
+ from nat.observability.processor.processor import Processor
29
+ from nat.utils.callable_utils import ainvoke_any
30
+ from nat.utils.type_utils import override
31
+
32
+ RedactionInputT = TypeVar('RedactionInputT')
33
+ RedactionDataT = TypeVar('RedactionDataT')
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ class RedactionProcessor(Processor[RedactionInputT, RedactionInputT], Generic[RedactionInputT, RedactionDataT]):
39
+ """Abstract base class for redaction processors."""
40
+
41
+ @abstractmethod
42
+ async def should_redact(self, item: RedactionInputT) -> bool:
43
+ """Determine if this item should be redacted.
44
+
45
+ Args:
46
+ item (RedactionInputT): The item to check.
47
+
48
+ Returns:
49
+ bool: True if the item should be redacted, False otherwise.
50
+ """
51
+ pass
52
+
53
+ @abstractmethod
54
+ async def redact_item(self, item: RedactionInputT) -> RedactionInputT:
55
+ """Redact the item.
56
+
57
+ Args:
58
+ item (RedactionInputT): The item to redact.
59
+
60
+ Returns:
61
+ RedactionInputT: The redacted item.
62
+ """
63
+ pass
64
+
65
+ @override
66
+ async def process(self, item: RedactionInputT) -> RedactionInputT:
67
+ """Perform redaction on the item if it should be redacted.
68
+
69
+ Args:
70
+ item (RedactionInputT): The item to process.
71
+
72
+ Returns:
73
+ RedactionInputT: The processed item.
74
+ """
75
+ if await self.should_redact(item):
76
+ return await self.redact_item(item)
77
+ return item
78
+
79
+
80
+ @dataclass
81
+ class RedactionContextState:
82
+ """Generic context state for redaction results.
83
+
84
+ Stores the redaction result in a context variable to avoid redundant
85
+ callback executions within the same request context.
86
+ """
87
+
88
+ redaction_result: ContextVar[bool
89
+ | None] = field(default_factory=lambda: ContextVar("redaction_result", default=None))
90
+
91
+
92
+ class RedactionManager(Generic[RedactionDataT]):
93
+ """Generic manager for atomic redaction operations.
94
+
95
+ Handles state mutations and ensures atomic callback execution
96
+ with proper result caching within a request context.
97
+
98
+ Args:
99
+ RedactionDataT: The type of data being processed for redaction decisions.
100
+ """
101
+
102
+ def __init__(self, context_state: RedactionContextState):
103
+ self._context_state = context_state
104
+
105
+ def set_redaction_result(self, result: bool) -> None:
106
+ """Set the redaction result in the context.
107
+
108
+ Args:
109
+ result (bool): The redaction result to cache.
110
+ """
111
+ self._context_state.redaction_result.set(result)
112
+
113
+ def clear_redaction_result(self) -> None:
114
+ """Clear the cached redaction result from the context."""
115
+ self._context_state.redaction_result.set(None)
116
+
117
+ async def redaction_check(self, callback: Callable[..., Any], data: RedactionDataT) -> bool:
118
+ """Execute redaction callback with atomic result caching.
119
+
120
+ Checks for existing cached results first, then executes the callback
121
+ and caches the result atomically. Since data is static per request,
122
+ subsequent calls within the same context return the cached result.
123
+
124
+ Supports sync/async functions, generators, and async generators.
125
+
126
+ Args:
127
+ callback (Callable[..., Any]): The callback to execute (sync/async function, generator, etc.).
128
+ data (RedactionDataT): The data to pass to the callback for redaction decision.
129
+
130
+ Returns:
131
+ bool: True if the item should be redacted, False otherwise.
132
+ """
133
+ # Check if we already have a result for this context
134
+ existing_result = self._context_state.redaction_result.get()
135
+ if existing_result is not None:
136
+ return existing_result
137
+
138
+ # Execute callback and cache result
139
+ result_value = await ainvoke_any(callback, data)
140
+ result = bool(result_value)
141
+ self.set_redaction_result(result)
142
+ return result
143
+
144
+
145
+ class RedactionContext(Generic[RedactionDataT]):
146
+ """Generic context provider for redaction operations.
147
+
148
+ Provides read-only access to redaction state and manages the
149
+ RedactionManager lifecycle through async context managers.
150
+
151
+ Args:
152
+ RedactionDataT: The type of data being processed for redaction decisions.
153
+ """
154
+
155
+ def __init__(self, context: RedactionContextState):
156
+ self._context_state: RedactionContextState = context
157
+
158
+ @property
159
+ def redaction_result(self) -> bool | None:
160
+ """Get the current redaction result from context.
161
+
162
+ Returns:
163
+ bool | None: The cached redaction result, or None if not set.
164
+ """
165
+ return self._context_state.redaction_result.get()
166
+
167
+ @asynccontextmanager
168
+ async def redaction_manager(self) -> AsyncGenerator[RedactionManager[RedactionDataT], None]:
169
+ """Provide a redaction manager within an async context.
170
+
171
+ Creates and yields a RedactionManager instance for atomic
172
+ redaction operations within the current context.
173
+
174
+ Yields:
175
+ RedactionManager[RedactionDataT]: Manager instance for redaction operations.
176
+ """
177
+ yield RedactionManager(self._context_state)
@@ -0,0 +1,92 @@
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 logging
17
+ from collections.abc import Callable
18
+ from typing import Any
19
+
20
+ from starlette.datastructures import Headers
21
+
22
+ from nat.builder.context import Context
23
+ from nat.observability.processor.redaction.contextual_span_redaction_processor import ContextualSpanRedactionProcessor
24
+ from nat.utils.type_utils import override
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class SpanHeaderRedactionProcessor(ContextualSpanRedactionProcessor[dict[str, Any]]):
30
+ """Processor that redacts the Span based on multiple headers and callback logic.
31
+
32
+ Uses context-scoped atomic updates to avoid redundant callback executions within a single context.
33
+ Since headers are static per request, the callback result is cached for the entire context using
34
+ an asynccontextmanager to ensure atomic operations.
35
+
36
+ Args:
37
+ headers: List of header keys to extract and pass to the callback
38
+ attributes: List of Span attribute keys to redact
39
+ callback: Callable that determines if redaction should occur
40
+ enabled: Whether the processor is enabled (default: True)
41
+ force_redact: If True, always redact regardless of header checks (default: False)
42
+ redaction_value: The value to replace redacted attributes with (default: "[REDACTED]")
43
+ """
44
+
45
+ def __init__(self,
46
+ headers: list[str],
47
+ attributes: list[str],
48
+ callback: Callable[..., Any],
49
+ enabled: bool = True,
50
+ force_redact: bool = False,
51
+ redaction_value: str = "[REDACTED]",
52
+ redaction_tag: str | None = None):
53
+ # Initialize the base class with common parameters
54
+ super().__init__(attributes=attributes,
55
+ callback=callback,
56
+ enabled=enabled,
57
+ force_redact=force_redact,
58
+ redaction_value=redaction_value,
59
+ redaction_tag=redaction_tag)
60
+ # Store header-specific configuration
61
+ self.headers = headers
62
+
63
+ @override
64
+ def extract_data_from_context(self) -> dict[str, Any] | None:
65
+ """Extract header data from the context.
66
+
67
+ Returns:
68
+ dict[str, Any] | None: Dictionary of header names to values, or None if no headers.
69
+ """
70
+
71
+ context = Context.get()
72
+ headers: Headers | None = context.metadata.headers
73
+
74
+ if headers is None or not self.headers:
75
+ return None
76
+
77
+ header_map: dict[str, Any] = {header: headers.get(header, None) for header in self.headers}
78
+
79
+ return header_map
80
+
81
+ @override
82
+ def validate_data(self, data: dict[str, Any]) -> bool:
83
+ """Validate that the extracted headers are suitable for callback execution.
84
+
85
+ Args:
86
+ data (dict[str, Any]): The extracted header dictionary.
87
+
88
+ Returns:
89
+ bool: True if headers exist and are not all None, False otherwise.
90
+ """
91
+ # Skip callback if no headers were found (all None values)
92
+ return bool(data) and not all(value is None for value in data.values())
@@ -0,0 +1,68 @@
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 logging
17
+ import os
18
+ from collections.abc import Mapping
19
+ from enum import Enum
20
+
21
+ from nat.data_models.span import Span
22
+ from nat.observability.processor.processor import Processor
23
+ from nat.utils.type_utils import override
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class SpanTaggingProcessor(Processor[Span, Span]):
29
+ """Processor that tags spans with multiple key-value metadata attributes.
30
+
31
+ This processor adds custom tags to spans by setting attributes with a configurable prefix.
32
+ Tags are applied for each key-value pair in the tags dictionary. The processor uses
33
+ a span prefix (configurable via NAT_SPAN_PREFIX environment variable) to namespace
34
+ the tag attributes.
35
+
36
+ Args:
37
+ tags: Mapping of tag keys to their values. Values can be enums (converted to strings) or strings
38
+ span_prefix: The prefix to use for tag attributes (default: from NAT_SPAN_PREFIX env var or "nat")
39
+ """
40
+
41
+ def __init__(self, tags: Mapping[str, Enum | str] | None = None, span_prefix: str | None = None):
42
+ self.tags = tags or {}
43
+
44
+ if span_prefix is None:
45
+ span_prefix = os.getenv("NAT_SPAN_PREFIX", "nat").strip() or "nat"
46
+
47
+ self._span_prefix = span_prefix
48
+
49
+ @override
50
+ async def process(self, item: Span) -> Span:
51
+ """Tag the span with all configured tags.
52
+
53
+ Args:
54
+ item (Span): The span to tag
55
+
56
+ Returns:
57
+ Span: The tagged span with all configured tags applied
58
+ """
59
+ for tag_key, tag_value in self.tags.items():
60
+ key = str(tag_key).strip()
61
+ if not key:
62
+ continue
63
+ value_str = str(tag_value.value) if isinstance(tag_value, Enum) else str(tag_value)
64
+ if value_str == "":
65
+ continue
66
+ item.set_attribute(f"{self._span_prefix}.{key}", value_str)
67
+
68
+ return item