nvidia-nat 1.3.0a20250910__py3-none-any.whl → 1.4.0a20251112__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 (213) hide show
  1. nat/agent/base.py +13 -8
  2. nat/agent/prompt_optimizer/prompt.py +68 -0
  3. nat/agent/prompt_optimizer/register.py +149 -0
  4. nat/agent/react_agent/agent.py +6 -5
  5. nat/agent/react_agent/register.py +49 -39
  6. nat/agent/reasoning_agent/reasoning_agent.py +17 -15
  7. nat/agent/register.py +2 -0
  8. nat/agent/responses_api_agent/__init__.py +14 -0
  9. nat/agent/responses_api_agent/register.py +126 -0
  10. nat/agent/rewoo_agent/agent.py +304 -117
  11. nat/agent/rewoo_agent/prompt.py +19 -22
  12. nat/agent/rewoo_agent/register.py +51 -38
  13. nat/agent/tool_calling_agent/agent.py +75 -17
  14. nat/agent/tool_calling_agent/register.py +46 -23
  15. nat/authentication/api_key/api_key_auth_provider.py +6 -11
  16. nat/authentication/api_key/api_key_auth_provider_config.py +8 -5
  17. nat/authentication/credential_validator/__init__.py +14 -0
  18. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  19. nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
  20. nat/authentication/interfaces.py +5 -2
  21. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +69 -36
  22. nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +2 -1
  23. nat/authentication/oauth2/oauth2_resource_server_config.py +125 -0
  24. nat/builder/builder.py +55 -23
  25. nat/builder/component_utils.py +9 -5
  26. nat/builder/context.py +54 -15
  27. nat/builder/eval_builder.py +14 -9
  28. nat/builder/framework_enum.py +1 -0
  29. nat/builder/front_end.py +1 -1
  30. nat/builder/function.py +370 -0
  31. nat/builder/function_info.py +1 -1
  32. nat/builder/intermediate_step_manager.py +38 -2
  33. nat/builder/workflow.py +5 -0
  34. nat/builder/workflow_builder.py +306 -54
  35. nat/cli/cli_utils/config_override.py +1 -1
  36. nat/cli/commands/info/info.py +16 -6
  37. nat/cli/commands/mcp/__init__.py +14 -0
  38. nat/cli/commands/mcp/mcp.py +986 -0
  39. nat/cli/commands/optimize.py +90 -0
  40. nat/cli/commands/start.py +1 -1
  41. nat/cli/commands/workflow/templates/config.yml.j2 +14 -13
  42. nat/cli/commands/workflow/templates/register.py.j2 +2 -2
  43. nat/cli/commands/workflow/templates/workflow.py.j2 +35 -21
  44. nat/cli/commands/workflow/workflow_commands.py +60 -18
  45. nat/cli/entrypoint.py +15 -11
  46. nat/cli/main.py +3 -0
  47. nat/cli/register_workflow.py +38 -4
  48. nat/cli/type_registry.py +72 -1
  49. nat/control_flow/__init__.py +0 -0
  50. nat/control_flow/register.py +20 -0
  51. nat/control_flow/router_agent/__init__.py +0 -0
  52. nat/control_flow/router_agent/agent.py +329 -0
  53. nat/control_flow/router_agent/prompt.py +48 -0
  54. nat/control_flow/router_agent/register.py +91 -0
  55. nat/control_flow/sequential_executor.py +166 -0
  56. nat/data_models/agent.py +34 -0
  57. nat/data_models/api_server.py +199 -69
  58. nat/data_models/authentication.py +23 -9
  59. nat/data_models/common.py +47 -0
  60. nat/data_models/component.py +2 -0
  61. nat/data_models/component_ref.py +11 -0
  62. nat/data_models/config.py +41 -17
  63. nat/data_models/dataset_handler.py +4 -3
  64. nat/data_models/function.py +34 -0
  65. nat/data_models/function_dependencies.py +8 -0
  66. nat/data_models/intermediate_step.py +9 -1
  67. nat/data_models/llm.py +15 -1
  68. nat/data_models/openai_mcp.py +46 -0
  69. nat/data_models/optimizable.py +208 -0
  70. nat/data_models/optimizer.py +161 -0
  71. nat/data_models/span.py +41 -3
  72. nat/data_models/thinking_mixin.py +2 -2
  73. nat/embedder/azure_openai_embedder.py +2 -1
  74. nat/embedder/nim_embedder.py +3 -2
  75. nat/embedder/openai_embedder.py +3 -2
  76. nat/eval/config.py +1 -1
  77. nat/eval/dataset_handler/dataset_downloader.py +3 -2
  78. nat/eval/dataset_handler/dataset_filter.py +34 -2
  79. nat/eval/evaluate.py +10 -3
  80. nat/eval/evaluator/base_evaluator.py +1 -1
  81. nat/eval/rag_evaluator/evaluate.py +7 -4
  82. nat/eval/register.py +4 -0
  83. nat/eval/runtime_evaluator/__init__.py +14 -0
  84. nat/eval/runtime_evaluator/evaluate.py +123 -0
  85. nat/eval/runtime_evaluator/register.py +100 -0
  86. nat/eval/swe_bench_evaluator/evaluate.py +1 -1
  87. nat/eval/trajectory_evaluator/register.py +1 -1
  88. nat/eval/tunable_rag_evaluator/evaluate.py +1 -1
  89. nat/eval/usage_stats.py +2 -0
  90. nat/eval/utils/output_uploader.py +3 -2
  91. nat/eval/utils/weave_eval.py +17 -3
  92. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  93. nat/experimental/test_time_compute/functions/execute_score_select_function.py +1 -1
  94. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  95. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +1 -1
  96. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +3 -3
  97. nat/experimental/test_time_compute/models/strategy_base.py +2 -2
  98. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -1
  99. nat/front_ends/console/authentication_flow_handler.py +82 -30
  100. nat/front_ends/console/console_front_end_plugin.py +19 -7
  101. nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +1 -1
  102. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  103. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  104. nat/front_ends/fastapi/fastapi_front_end_config.py +25 -3
  105. nat/front_ends/fastapi/fastapi_front_end_plugin.py +140 -3
  106. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +445 -265
  107. nat/front_ends/fastapi/job_store.py +518 -99
  108. nat/front_ends/fastapi/main.py +11 -19
  109. nat/front_ends/fastapi/message_handler.py +69 -44
  110. nat/front_ends/fastapi/message_validator.py +8 -7
  111. nat/front_ends/fastapi/utils.py +57 -0
  112. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  113. nat/front_ends/mcp/mcp_front_end_config.py +71 -3
  114. nat/front_ends/mcp/mcp_front_end_plugin.py +85 -21
  115. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +248 -29
  116. nat/front_ends/mcp/memory_profiler.py +320 -0
  117. nat/front_ends/mcp/tool_converter.py +78 -25
  118. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  119. nat/llm/aws_bedrock_llm.py +21 -8
  120. nat/llm/azure_openai_llm.py +14 -5
  121. nat/llm/litellm_llm.py +80 -0
  122. nat/llm/nim_llm.py +23 -9
  123. nat/llm/openai_llm.py +19 -7
  124. nat/llm/register.py +4 -0
  125. nat/llm/utils/thinking.py +1 -1
  126. nat/observability/exporter/base_exporter.py +1 -1
  127. nat/observability/exporter/processing_exporter.py +29 -55
  128. nat/observability/exporter/span_exporter.py +43 -15
  129. nat/observability/exporter_manager.py +2 -2
  130. nat/observability/mixin/redaction_config_mixin.py +5 -4
  131. nat/observability/mixin/tagging_config_mixin.py +26 -14
  132. nat/observability/mixin/type_introspection_mixin.py +420 -107
  133. nat/observability/processor/batching_processor.py +1 -1
  134. nat/observability/processor/processor.py +3 -0
  135. nat/observability/processor/redaction/__init__.py +24 -0
  136. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  137. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  138. nat/observability/processor/redaction/redaction_processor.py +177 -0
  139. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  140. nat/observability/processor/span_tagging_processor.py +21 -14
  141. nat/observability/register.py +16 -0
  142. nat/profiler/callbacks/langchain_callback_handler.py +32 -7
  143. nat/profiler/callbacks/llama_index_callback_handler.py +36 -2
  144. nat/profiler/callbacks/token_usage_base_model.py +2 -0
  145. nat/profiler/decorators/framework_wrapper.py +61 -9
  146. nat/profiler/decorators/function_tracking.py +35 -3
  147. nat/profiler/forecasting/models/linear_model.py +1 -1
  148. nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
  149. nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
  150. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +1 -1
  151. nat/profiler/parameter_optimization/__init__.py +0 -0
  152. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  153. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  154. nat/profiler/parameter_optimization/parameter_optimizer.py +189 -0
  155. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  156. nat/profiler/parameter_optimization/pareto_visualizer.py +460 -0
  157. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  158. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  159. nat/profiler/utils.py +3 -1
  160. nat/registry_handlers/pypi/register_pypi.py +5 -3
  161. nat/registry_handlers/rest/register_rest.py +5 -3
  162. nat/retriever/milvus/retriever.py +1 -1
  163. nat/retriever/nemo_retriever/register.py +2 -1
  164. nat/runtime/loader.py +1 -1
  165. nat/runtime/runner.py +111 -6
  166. nat/runtime/session.py +49 -3
  167. nat/settings/global_settings.py +2 -2
  168. nat/tool/chat_completion.py +4 -1
  169. nat/tool/code_execution/code_sandbox.py +3 -6
  170. nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +19 -32
  171. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +6 -1
  172. nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +2 -0
  173. nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +10 -4
  174. nat/tool/datetime_tools.py +1 -1
  175. nat/tool/github_tools.py +450 -0
  176. nat/tool/memory_tools/add_memory_tool.py +3 -3
  177. nat/tool/memory_tools/delete_memory_tool.py +3 -4
  178. nat/tool/memory_tools/get_memory_tool.py +4 -4
  179. nat/tool/register.py +2 -7
  180. nat/tool/server_tools.py +15 -2
  181. nat/utils/__init__.py +76 -0
  182. nat/utils/callable_utils.py +70 -0
  183. nat/utils/data_models/schema_validator.py +1 -1
  184. nat/utils/decorators.py +210 -0
  185. nat/utils/exception_handlers/automatic_retries.py +278 -72
  186. nat/utils/io/yaml_tools.py +73 -3
  187. nat/utils/log_levels.py +25 -0
  188. nat/utils/responses_api.py +26 -0
  189. nat/utils/string_utils.py +16 -0
  190. nat/utils/type_converter.py +12 -3
  191. nat/utils/type_utils.py +6 -2
  192. nvidia_nat-1.4.0a20251112.dist-info/METADATA +197 -0
  193. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/RECORD +199 -165
  194. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/entry_points.txt +1 -0
  195. nat/cli/commands/info/list_mcp.py +0 -461
  196. nat/data_models/temperature_mixin.py +0 -43
  197. nat/data_models/top_p_mixin.py +0 -43
  198. nat/observability/processor/header_redaction_processor.py +0 -123
  199. nat/observability/processor/redaction_processor.py +0 -77
  200. nat/tool/code_execution/test_code_execution_sandbox.py +0 -414
  201. nat/tool/github_tools/create_github_commit.py +0 -133
  202. nat/tool/github_tools/create_github_issue.py +0 -87
  203. nat/tool/github_tools/create_github_pr.py +0 -106
  204. nat/tool/github_tools/get_github_file.py +0 -106
  205. nat/tool/github_tools/get_github_issue.py +0 -166
  206. nat/tool/github_tools/get_github_pr.py +0 -256
  207. nat/tool/github_tools/update_github_issue.py +0 -100
  208. nvidia_nat-1.3.0a20250910.dist-info/METADATA +0 -373
  209. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  210. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/WHEEL +0 -0
  211. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  212. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE.md +0 -0
  213. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/top_level.txt +0 -0
@@ -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())
@@ -15,6 +15,8 @@
15
15
 
16
16
  import logging
17
17
  import os
18
+ from collections.abc import Mapping
19
+ from enum import Enum
18
20
 
19
21
  from nat.data_models.span import Span
20
22
  from nat.observability.processor.processor import Processor
@@ -24,22 +26,20 @@ logger = logging.getLogger(__name__)
24
26
 
25
27
 
26
28
  class SpanTaggingProcessor(Processor[Span, Span]):
27
- """Processor that tags spans with key-value metadata attributes.
29
+ """Processor that tags spans with multiple key-value metadata attributes.
28
30
 
29
31
  This processor adds custom tags to spans by setting attributes with a configurable prefix.
30
- Tags are only applied when both tag_key and tag_value are provided. The processor uses
32
+ Tags are applied for each key-value pair in the tags dictionary. The processor uses
31
33
  a span prefix (configurable via NAT_SPAN_PREFIX environment variable) to namespace
32
34
  the tag attributes.
33
35
 
34
- Args:
35
- tag_key: The key name for the tag to add to spans.
36
- tag_value: The value for the tag to add to spans.
37
- span_prefix: The prefix to use for tag attributes (default: from NAT_SPAN_PREFIX env var or "nat").
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")
38
39
  """
39
40
 
40
- def __init__(self, tag_key: str | None = None, tag_value: str | None = None, span_prefix: str | None = None):
41
- self.tag_key = tag_key
42
- self.tag_value = tag_value
41
+ def __init__(self, tags: Mapping[str, Enum | str] | None = None, span_prefix: str | None = None):
42
+ self.tags = tags or {}
43
43
 
44
44
  if span_prefix is None:
45
45
  span_prefix = os.getenv("NAT_SPAN_PREFIX", "nat").strip() or "nat"
@@ -48,14 +48,21 @@ class SpanTaggingProcessor(Processor[Span, Span]):
48
48
 
49
49
  @override
50
50
  async def process(self, item: Span) -> Span:
51
- """Tag the span with a tag if both tag_key and tag_value are provided.
51
+ """Tag the span with all configured tags.
52
52
 
53
53
  Args:
54
- item (Span): The span to tag.
54
+ item (Span): The span to tag
55
55
 
56
56
  Returns:
57
- Span: The tagged span.
57
+ Span: The tagged span with all configured tags applied
58
58
  """
59
- if self.tag_key and self.tag_value:
60
- item.set_attribute(f"{self._span_prefix}.{self.tag_key}", self.tag_value)
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
+
61
68
  return item
@@ -77,6 +77,14 @@ async def console_logging_method(config: ConsoleLoggingMethodConfig, builder: Bu
77
77
  level = getattr(logging, config.level.upper(), logging.INFO)
78
78
  handler = logging.StreamHandler(stream=sys.stdout)
79
79
  handler.setLevel(level)
80
+
81
+ # Set formatter to match the default CLI format
82
+ formatter = logging.Formatter(
83
+ fmt="%(asctime)s - %(levelname)-8s - %(name)s:%(lineno)d - %(message)s",
84
+ datefmt="%Y-%m-%d %H:%M:%S",
85
+ )
86
+ handler.setFormatter(formatter)
87
+
80
88
  yield handler
81
89
 
82
90
 
@@ -95,4 +103,12 @@ async def file_logging_method(config: FileLoggingMethod, builder: Builder):
95
103
  level = getattr(logging, config.level.upper(), logging.INFO)
96
104
  handler = logging.FileHandler(filename=config.path, mode="a", encoding="utf-8")
97
105
  handler.setLevel(level)
106
+
107
+ # Set formatter to match the default CLI format
108
+ formatter = logging.Formatter(
109
+ fmt="%(asctime)s - %(levelname)-8s - %(name)s:%(lineno)d - %(message)s",
110
+ datefmt="%Y-%m-%d %H:%M:%S",
111
+ )
112
+ handler.setFormatter(formatter)
113
+
98
114
  yield handler
@@ -33,6 +33,7 @@ from nat.builder.context import Context
33
33
  from nat.builder.framework_enum import LLMFrameworkEnum
34
34
  from nat.data_models.intermediate_step import IntermediateStepPayload
35
35
  from nat.data_models.intermediate_step import IntermediateStepType
36
+ from nat.data_models.intermediate_step import ServerToolUseSchema
36
37
  from nat.data_models.intermediate_step import StreamEventData
37
38
  from nat.data_models.intermediate_step import ToolSchema
38
39
  from nat.data_models.intermediate_step import TraceMetadata
@@ -48,7 +49,14 @@ def _extract_tools_schema(invocation_params: dict) -> list:
48
49
  tools_schema = []
49
50
  if invocation_params is not None:
50
51
  for tool in invocation_params.get("tools", []):
51
- tools_schema.append(ToolSchema(**tool))
52
+ try:
53
+ tools_schema.append(ToolSchema(**tool))
54
+ except Exception:
55
+ logger.debug(
56
+ "Failed to parse tool schema from invocation params: %s. \n This "
57
+ "can occur when the LLM server has native tools and can be ignored if "
58
+ "using the responses API.",
59
+ tool)
52
60
 
53
61
  return tools_schema
54
62
 
@@ -93,11 +101,15 @@ class LangchainProfilerHandler(AsyncCallbackHandler, BaseProfilerCallback):
93
101
  completion_tokens = usage_metadata.get("output_tokens", 0)
94
102
  total_tokens = usage_metadata.get("total_tokens", 0)
95
103
 
96
- return TokenUsageBaseModel(
97
- prompt_tokens=prompt_tokens,
98
- completion_tokens=completion_tokens,
99
- total_tokens=total_tokens,
100
- )
104
+ cache_tokens = usage_metadata.get("input_token_details", {}).get("cache_read", 0)
105
+
106
+ reasoning_tokens = usage_metadata.get("output_token_details", {}).get("reasoning", 0)
107
+
108
+ return TokenUsageBaseModel(prompt_tokens=prompt_tokens,
109
+ completion_tokens=completion_tokens,
110
+ total_tokens=total_tokens,
111
+ cached_tokens=cache_tokens,
112
+ reasoning_tokens=reasoning_tokens)
101
113
  return TokenUsageBaseModel()
102
114
 
103
115
  async def on_llm_start(self, serialized: dict[str, Any], prompts: list[str], **kwargs: Any) -> None:
@@ -213,6 +225,7 @@ class LangchainProfilerHandler(AsyncCallbackHandler, BaseProfilerCallback):
213
225
  except IndexError:
214
226
  generation = None
215
227
 
228
+ message = None
216
229
  if isinstance(generation, ChatGeneration):
217
230
  try:
218
231
  message = generation.message
@@ -232,6 +245,17 @@ class LangchainProfilerHandler(AsyncCallbackHandler, BaseProfilerCallback):
232
245
  else:
233
246
  llm_text_output = ""
234
247
 
248
+ tool_outputs_list = []
249
+ # Check if message.additional_kwargs as tool_outputs indicative of server side tool calling
250
+ if message and message.additional_kwargs and "tool_outputs" in message.additional_kwargs:
251
+ tools_outputs = message.additional_kwargs["tool_outputs"]
252
+ if isinstance(tools_outputs, list):
253
+ for tool in tools_outputs:
254
+ try:
255
+ tool_outputs_list.append(ServerToolUseSchema(**tool))
256
+ except Exception:
257
+ pass
258
+
235
259
  # update shared state behind lock
236
260
  with self._lock:
237
261
  usage_stat = IntermediateStepPayload(
@@ -243,7 +267,8 @@ class LangchainProfilerHandler(AsyncCallbackHandler, BaseProfilerCallback):
243
267
  data=StreamEventData(input=self._run_id_to_llm_input.get(str(kwargs.get("run_id", "")), ""),
244
268
  output=llm_text_output),
245
269
  usage_info=UsageInfo(token_usage=self._extract_token_base_model(usage_metadata)),
246
- metadata=TraceMetadata(chat_responses=[generation] if generation else []))
270
+ metadata=TraceMetadata(chat_responses=[generation] if generation else [],
271
+ tool_outputs=tool_outputs_list if tool_outputs_list else []))
247
272
 
248
273
  self.step_manager.push_intermediate_step(usage_stat)
249
274