nvidia-nat 1.3.0a20250909__py3-none-any.whl → 1.3.0a20250917__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.
- nat/agent/base.py +11 -6
- nat/agent/dual_node.py +2 -2
- nat/agent/prompt_optimizer/prompt.py +68 -0
- nat/agent/prompt_optimizer/register.py +149 -0
- nat/agent/react_agent/agent.py +1 -1
- nat/agent/react_agent/register.py +17 -7
- nat/agent/reasoning_agent/reasoning_agent.py +6 -1
- nat/agent/register.py +2 -0
- nat/agent/rewoo_agent/agent.py +6 -3
- nat/agent/rewoo_agent/register.py +16 -10
- nat/agent/router_agent/__init__.py +0 -0
- nat/agent/router_agent/agent.py +329 -0
- nat/agent/router_agent/prompt.py +48 -0
- nat/agent/router_agent/register.py +97 -0
- nat/agent/tool_calling_agent/agent.py +69 -7
- nat/agent/tool_calling_agent/register.py +17 -9
- nat/builder/builder.py +27 -4
- nat/builder/component_utils.py +7 -3
- nat/builder/function.py +167 -0
- nat/builder/function_info.py +1 -1
- nat/builder/workflow.py +5 -0
- nat/builder/workflow_builder.py +213 -16
- nat/cli/commands/optimize.py +90 -0
- nat/cli/commands/workflow/templates/config.yml.j2 +0 -1
- nat/cli/commands/workflow/workflow_commands.py +5 -8
- nat/cli/entrypoint.py +2 -0
- nat/cli/register_workflow.py +38 -4
- nat/cli/type_registry.py +71 -0
- nat/data_models/api_server.py +1 -1
- nat/data_models/component.py +2 -0
- nat/data_models/component_ref.py +11 -0
- nat/data_models/config.py +40 -16
- nat/data_models/function.py +34 -0
- nat/data_models/function_dependencies.py +8 -0
- nat/data_models/optimizable.py +119 -0
- nat/data_models/optimizer.py +149 -0
- nat/data_models/temperature_mixin.py +4 -3
- nat/data_models/top_p_mixin.py +4 -3
- nat/embedder/nim_embedder.py +1 -1
- nat/embedder/openai_embedder.py +1 -1
- nat/eval/config.py +1 -1
- nat/eval/evaluate.py +5 -1
- nat/eval/register.py +4 -0
- nat/eval/runtime_evaluator/__init__.py +14 -0
- nat/eval/runtime_evaluator/evaluate.py +123 -0
- nat/eval/runtime_evaluator/register.py +100 -0
- nat/experimental/test_time_compute/functions/plan_select_execute_function.py +5 -1
- nat/front_ends/fastapi/dask_client_mixin.py +43 -0
- nat/front_ends/fastapi/fastapi_front_end_config.py +14 -3
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +111 -3
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +243 -228
- nat/front_ends/fastapi/job_store.py +518 -99
- nat/front_ends/fastapi/main.py +11 -19
- nat/front_ends/fastapi/utils.py +57 -0
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +3 -2
- nat/llm/aws_bedrock_llm.py +15 -4
- nat/llm/nim_llm.py +14 -3
- nat/llm/openai_llm.py +8 -1
- nat/observability/exporter/processing_exporter.py +29 -55
- nat/observability/mixin/redaction_config_mixin.py +5 -4
- nat/observability/mixin/tagging_config_mixin.py +26 -14
- nat/observability/mixin/type_introspection_mixin.py +401 -107
- nat/observability/processor/processor.py +3 -0
- nat/observability/processor/redaction/__init__.py +24 -0
- nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
- nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
- nat/observability/processor/redaction/redaction_processor.py +177 -0
- nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
- nat/observability/processor/span_tagging_processor.py +21 -14
- nat/profiler/decorators/framework_wrapper.py +9 -6
- nat/profiler/parameter_optimization/__init__.py +0 -0
- nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
- nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
- nat/profiler/parameter_optimization/parameter_optimizer.py +149 -0
- nat/profiler/parameter_optimization/parameter_selection.py +108 -0
- nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
- nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
- nat/profiler/parameter_optimization/update_helpers.py +66 -0
- nat/profiler/utils.py +3 -1
- nat/tool/chat_completion.py +5 -2
- nat/tool/document_search.py +1 -1
- nat/tool/github_tools.py +450 -0
- nat/tool/register.py +2 -7
- nat/utils/callable_utils.py +70 -0
- nat/utils/exception_handlers/automatic_retries.py +103 -48
- nat/utils/type_utils.py +4 -0
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/METADATA +8 -1
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/RECORD +94 -74
- nat/observability/processor/header_redaction_processor.py +0 -123
- nat/observability/processor/redaction_processor.py +0 -77
- nat/tool/github_tools/create_github_commit.py +0 -133
- nat/tool/github_tools/create_github_issue.py +0 -87
- nat/tool/github_tools/create_github_pr.py +0 -106
- nat/tool/github_tools/get_github_file.py +0 -106
- nat/tool/github_tools/get_github_issue.py +0 -166
- nat/tool/github_tools/get_github_pr.py +0 -256
- nat/tool/github_tools/update_github_issue.py +0 -100
- /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/WHEEL +0 -0
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/entry_points.txt +0 -0
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/licenses/LICENSE.md +0 -0
- {nvidia_nat-1.3.0a20250909.dist-info → nvidia_nat-1.3.0a20250917.dist-info}/top_level.txt +0 -0
nat/front_ends/fastapi/main.py
CHANGED
|
@@ -13,19 +13,24 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
|
-
import importlib
|
|
17
16
|
import logging
|
|
18
17
|
import os
|
|
18
|
+
import typing
|
|
19
19
|
|
|
20
20
|
from nat.front_ends.fastapi.fastapi_front_end_plugin_worker import FastApiFrontEndPluginWorkerBase
|
|
21
|
+
from nat.front_ends.fastapi.utils import get_config_file_path
|
|
22
|
+
from nat.front_ends.fastapi.utils import import_class_from_string
|
|
21
23
|
from nat.runtime.loader import load_config
|
|
22
24
|
|
|
25
|
+
if typing.TYPE_CHECKING:
|
|
26
|
+
from fastapi import FastAPI
|
|
27
|
+
|
|
23
28
|
logger = logging.getLogger(__name__)
|
|
24
29
|
|
|
25
30
|
|
|
26
|
-
def get_app():
|
|
31
|
+
def get_app() -> "FastAPI":
|
|
27
32
|
|
|
28
|
-
config_file_path =
|
|
33
|
+
config_file_path = get_config_file_path()
|
|
29
34
|
front_end_worker_full_name = os.getenv("NAT_FRONT_END_WORKER")
|
|
30
35
|
|
|
31
36
|
if (not config_file_path):
|
|
@@ -36,28 +41,15 @@ def get_app():
|
|
|
36
41
|
|
|
37
42
|
# Try to import the front end worker class
|
|
38
43
|
try:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
front_end_worker_module_name = ".".join(front_end_worker_parts[:-1])
|
|
43
|
-
front_end_worker_class_name = front_end_worker_parts[-1]
|
|
44
|
-
|
|
45
|
-
front_end_worker_module = importlib.import_module(front_end_worker_module_name)
|
|
46
|
-
|
|
47
|
-
if not hasattr(front_end_worker_module, front_end_worker_class_name):
|
|
48
|
-
raise ValueError(f"Front end worker {front_end_worker_full_name} not found.")
|
|
49
|
-
|
|
50
|
-
front_end_worker_class: type[FastApiFrontEndPluginWorkerBase] = getattr(front_end_worker_module,
|
|
51
|
-
front_end_worker_class_name)
|
|
44
|
+
front_end_worker_class: type[FastApiFrontEndPluginWorkerBase] = import_class_from_string(
|
|
45
|
+
front_end_worker_full_name)
|
|
52
46
|
|
|
53
47
|
if (not issubclass(front_end_worker_class, FastApiFrontEndPluginWorkerBase)):
|
|
54
48
|
raise ValueError(
|
|
55
49
|
f"Front end worker {front_end_worker_full_name} is not a subclass of FastApiFrontEndPluginWorker.")
|
|
56
50
|
|
|
57
51
|
# Load the config
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
config = load_config(abs_config_file_path)
|
|
52
|
+
config = load_config(config_file_path)
|
|
61
53
|
|
|
62
54
|
# Create an instance of the front end worker class
|
|
63
55
|
front_end_worker = front_end_worker_class(config)
|
|
@@ -0,0 +1,57 @@
|
|
|
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 importlib
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_config_file_path() -> str:
|
|
21
|
+
"""
|
|
22
|
+
Get the path to the NAT configuration file from the environment variable NAT_CONFIG_FILE.
|
|
23
|
+
Raises ValueError if the environment variable is not set.
|
|
24
|
+
"""
|
|
25
|
+
config_file_path = os.getenv("NAT_CONFIG_FILE")
|
|
26
|
+
if (not config_file_path):
|
|
27
|
+
raise ValueError("Config file not found in environment variable NAT_CONFIG_FILE.")
|
|
28
|
+
|
|
29
|
+
return os.path.abspath(config_file_path)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def import_class_from_string(class_full_name: str) -> type:
|
|
33
|
+
"""
|
|
34
|
+
Import a class from a string in the format 'module.submodule.ClassName'.
|
|
35
|
+
Raises ImportError if the class cannot be imported.
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
class_name_parts = class_full_name.split(".")
|
|
39
|
+
|
|
40
|
+
module_name = ".".join(class_name_parts[:-1])
|
|
41
|
+
class_name = class_name_parts[-1]
|
|
42
|
+
|
|
43
|
+
module = importlib.import_module(module_name)
|
|
44
|
+
|
|
45
|
+
if not hasattr(module, class_name):
|
|
46
|
+
raise ValueError(f"Class '{class_full_name}' not found.")
|
|
47
|
+
|
|
48
|
+
return getattr(module, class_name)
|
|
49
|
+
except (ImportError, AttributeError) as e:
|
|
50
|
+
raise ImportError(f"Could not import {class_full_name}.") from e
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_class_name(cls: type) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Get the full class name including the module.
|
|
56
|
+
"""
|
|
57
|
+
return f"{cls.__module__}.{cls.__qualname__}"
|
|
@@ -94,8 +94,9 @@ class MCPFrontEndPluginWorkerBase(ABC):
|
|
|
94
94
|
functions: dict[str, Function] = {}
|
|
95
95
|
|
|
96
96
|
# Extract all functions from the workflow
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
functions.update(workflow.functions)
|
|
98
|
+
for function_group in workflow.function_groups.values():
|
|
99
|
+
functions.update(function_group.get_accessible_functions())
|
|
99
100
|
|
|
100
101
|
functions[workflow.config.workflow.type] = workflow
|
|
101
102
|
|
nat/llm/aws_bedrock_llm.py
CHANGED
|
@@ -21,27 +21,38 @@ from nat.builder.builder import Builder
|
|
|
21
21
|
from nat.builder.llm import LLMProviderInfo
|
|
22
22
|
from nat.cli.register_workflow import register_llm_provider
|
|
23
23
|
from nat.data_models.llm import LLMBaseConfig
|
|
24
|
+
from nat.data_models.optimizable import OptimizableField
|
|
25
|
+
from nat.data_models.optimizable import OptimizableMixin
|
|
26
|
+
from nat.data_models.optimizable import SearchSpace
|
|
24
27
|
from nat.data_models.retry_mixin import RetryMixin
|
|
25
28
|
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
26
29
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
27
30
|
from nat.data_models.top_p_mixin import TopPMixin
|
|
28
31
|
|
|
29
32
|
|
|
30
|
-
class AWSBedrockModelConfig(LLMBaseConfig,
|
|
33
|
+
class AWSBedrockModelConfig(LLMBaseConfig,
|
|
34
|
+
RetryMixin,
|
|
35
|
+
OptimizableMixin,
|
|
36
|
+
TemperatureMixin,
|
|
37
|
+
TopPMixin,
|
|
38
|
+
ThinkingMixin,
|
|
39
|
+
name="aws_bedrock"):
|
|
31
40
|
"""An AWS Bedrock llm provider to be used with an LLM client."""
|
|
32
41
|
|
|
33
|
-
model_config = ConfigDict(protected_namespaces=())
|
|
42
|
+
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
34
43
|
|
|
35
44
|
# Completion parameters
|
|
36
45
|
model_name: str = Field(validation_alias=AliasChoices("model_name", "model"),
|
|
37
46
|
serialization_alias="model",
|
|
38
47
|
description="The model name for the hosted AWS Bedrock.")
|
|
39
|
-
max_tokens: int
|
|
48
|
+
max_tokens: int = OptimizableField(default=300,
|
|
49
|
+
description="Maximum number of tokens to generate.",
|
|
50
|
+
space=SearchSpace(high=2176, low=128, step=512))
|
|
40
51
|
context_size: int | None = Field(
|
|
41
52
|
default=1024,
|
|
42
53
|
gt=0,
|
|
43
54
|
description="The maximum number of tokens available for input. This is only required for LlamaIndex. "
|
|
44
|
-
"This field is ignored for
|
|
55
|
+
"This field is ignored for LangChain/LangGraph.",
|
|
45
56
|
)
|
|
46
57
|
|
|
47
58
|
# Client parameters
|
nat/llm/nim_llm.py
CHANGED
|
@@ -22,23 +22,34 @@ from nat.builder.builder import Builder
|
|
|
22
22
|
from nat.builder.llm import LLMProviderInfo
|
|
23
23
|
from nat.cli.register_workflow import register_llm_provider
|
|
24
24
|
from nat.data_models.llm import LLMBaseConfig
|
|
25
|
+
from nat.data_models.optimizable import OptimizableField
|
|
26
|
+
from nat.data_models.optimizable import OptimizableMixin
|
|
27
|
+
from nat.data_models.optimizable import SearchSpace
|
|
25
28
|
from nat.data_models.retry_mixin import RetryMixin
|
|
26
29
|
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
27
30
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
28
31
|
from nat.data_models.top_p_mixin import TopPMixin
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
class NIMModelConfig(LLMBaseConfig,
|
|
34
|
+
class NIMModelConfig(LLMBaseConfig,
|
|
35
|
+
RetryMixin,
|
|
36
|
+
OptimizableMixin,
|
|
37
|
+
TemperatureMixin,
|
|
38
|
+
TopPMixin,
|
|
39
|
+
ThinkingMixin,
|
|
40
|
+
name="nim"):
|
|
32
41
|
"""An NVIDIA Inference Microservice (NIM) llm provider to be used with an LLM client."""
|
|
33
42
|
|
|
34
|
-
model_config = ConfigDict(protected_namespaces=())
|
|
43
|
+
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
35
44
|
|
|
36
45
|
api_key: str | None = Field(default=None, description="NVIDIA API key to interact with hosted NIM.")
|
|
37
46
|
base_url: str | None = Field(default=None, description="Base url to the hosted NIM.")
|
|
38
47
|
model_name: str = Field(validation_alias=AliasChoices("model_name", "model"),
|
|
39
48
|
serialization_alias="model",
|
|
40
49
|
description="The model name for the hosted NIM.")
|
|
41
|
-
max_tokens: PositiveInt =
|
|
50
|
+
max_tokens: PositiveInt = OptimizableField(default=300,
|
|
51
|
+
description="Maximum number of tokens to generate.",
|
|
52
|
+
space=SearchSpace(high=2176, low=128, step=512))
|
|
42
53
|
|
|
43
54
|
|
|
44
55
|
@register_llm_provider(config_type=NIMModelConfig)
|
nat/llm/openai_llm.py
CHANGED
|
@@ -21,13 +21,20 @@ from nat.builder.builder import Builder
|
|
|
21
21
|
from nat.builder.llm import LLMProviderInfo
|
|
22
22
|
from nat.cli.register_workflow import register_llm_provider
|
|
23
23
|
from nat.data_models.llm import LLMBaseConfig
|
|
24
|
+
from nat.data_models.optimizable import OptimizableMixin
|
|
24
25
|
from nat.data_models.retry_mixin import RetryMixin
|
|
25
26
|
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
26
27
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
27
28
|
from nat.data_models.top_p_mixin import TopPMixin
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
class OpenAIModelConfig(LLMBaseConfig,
|
|
31
|
+
class OpenAIModelConfig(LLMBaseConfig,
|
|
32
|
+
RetryMixin,
|
|
33
|
+
OptimizableMixin,
|
|
34
|
+
TemperatureMixin,
|
|
35
|
+
TopPMixin,
|
|
36
|
+
ThinkingMixin,
|
|
37
|
+
name="openai"):
|
|
31
38
|
"""An OpenAI LLM provider to be used with an LLM client."""
|
|
32
39
|
|
|
33
40
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
@@ -53,6 +53,8 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
53
53
|
- Configurable None filtering: processors returning None can drop items from pipeline
|
|
54
54
|
- Automatic type validation before export
|
|
55
55
|
"""
|
|
56
|
+
# All ProcessingExporter instances automatically use this for signature checking
|
|
57
|
+
_signature_method = '_process_pipeline'
|
|
56
58
|
|
|
57
59
|
def __init__(self, context_state: ContextState | None = None, drop_nones: bool = True):
|
|
58
60
|
"""Initialize the processing exporter.
|
|
@@ -294,8 +296,6 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
294
296
|
self._check_processor_compatibility(predecessor,
|
|
295
297
|
processor,
|
|
296
298
|
"predecessor",
|
|
297
|
-
predecessor.output_class,
|
|
298
|
-
processor.input_class,
|
|
299
299
|
str(predecessor.output_type),
|
|
300
300
|
str(processor.input_type))
|
|
301
301
|
|
|
@@ -304,8 +304,6 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
304
304
|
self._check_processor_compatibility(processor,
|
|
305
305
|
successor,
|
|
306
306
|
"successor",
|
|
307
|
-
processor.output_class,
|
|
308
|
-
successor.input_class,
|
|
309
307
|
str(processor.output_type),
|
|
310
308
|
str(successor.input_type))
|
|
311
309
|
|
|
@@ -313,34 +311,22 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
313
311
|
source_processor: Processor,
|
|
314
312
|
target_processor: Processor,
|
|
315
313
|
relationship: str,
|
|
316
|
-
source_class: type,
|
|
317
|
-
target_class: type,
|
|
318
314
|
source_type: str,
|
|
319
315
|
target_type: str) -> None:
|
|
320
|
-
"""Check type compatibility between two processors.
|
|
316
|
+
"""Check type compatibility between two processors using Pydantic validation.
|
|
321
317
|
|
|
322
318
|
Args:
|
|
323
319
|
source_processor (Processor): The processor providing output
|
|
324
320
|
target_processor (Processor): The processor receiving input
|
|
325
321
|
relationship (str): Description of relationship ("predecessor" or "successor")
|
|
326
|
-
source_class (type): The output class of source processor
|
|
327
|
-
target_class (type): The input class of target processor
|
|
328
322
|
source_type (str): String representation of source type
|
|
329
323
|
target_type (str): String representation of target type
|
|
330
324
|
"""
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
except TypeError:
|
|
337
|
-
logger.warning(
|
|
338
|
-
"Cannot use issubclass() for type compatibility check between "
|
|
339
|
-
"%s (%s) and %s (%s). Skipping compatibility check.",
|
|
340
|
-
source_processor.__class__.__name__,
|
|
341
|
-
source_type,
|
|
342
|
-
target_processor.__class__.__name__,
|
|
343
|
-
target_type)
|
|
325
|
+
# Use Pydantic-based type compatibility checking
|
|
326
|
+
if not source_processor.is_output_compatible_with(target_processor.input_type):
|
|
327
|
+
raise ValueError(f"Processor {target_processor.__class__.__name__} input type {target_type} "
|
|
328
|
+
f"is not compatible with {relationship} {source_processor.__class__.__name__} "
|
|
329
|
+
f"output type {source_type}")
|
|
344
330
|
|
|
345
331
|
async def _pre_start(self) -> None:
|
|
346
332
|
|
|
@@ -350,36 +336,21 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
350
336
|
last_processor = self._processors[-1]
|
|
351
337
|
|
|
352
338
|
# validate that the first processor's input type is compatible with the exporter's input type
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
# Handle cases where classes are generic types that can't be used with issubclass
|
|
360
|
-
logger.warning(
|
|
361
|
-
"Cannot validate type compatibility between %s (%s) "
|
|
362
|
-
"and exporter (%s): %s. Skipping validation.",
|
|
363
|
-
first_processor.__class__.__name__,
|
|
364
|
-
first_processor.input_type,
|
|
365
|
-
self.input_type,
|
|
366
|
-
e)
|
|
367
|
-
|
|
339
|
+
if not first_processor.is_compatible_with_input(self.input_type):
|
|
340
|
+
logger.error("First processor %s input=%s incompatible with exporter input=%s",
|
|
341
|
+
first_processor.__class__.__name__,
|
|
342
|
+
first_processor.input_type,
|
|
343
|
+
self.input_type)
|
|
344
|
+
raise ValueError("First processor incompatible with exporter input")
|
|
368
345
|
# Validate that the last processor's output type is compatible with the exporter's output type
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
"Cannot validate type compatibility between %s (%s) "
|
|
378
|
-
"and exporter (%s): %s. Skipping validation.",
|
|
379
|
-
last_processor.__class__.__name__,
|
|
380
|
-
last_processor.output_type,
|
|
381
|
-
self.output_type,
|
|
382
|
-
e)
|
|
346
|
+
# Use DecomposedType.is_type_compatible for the final export stage to allow batch compatibility
|
|
347
|
+
# This enables BatchingProcessor[T] -> Exporter[T] patterns where the exporter handles both T and list[T]
|
|
348
|
+
if not DecomposedType.is_type_compatible(last_processor.output_type, self.output_type):
|
|
349
|
+
logger.error("Last processor %s output=%s incompatible with exporter output=%s",
|
|
350
|
+
last_processor.__class__.__name__,
|
|
351
|
+
last_processor.output_type,
|
|
352
|
+
self.output_type)
|
|
353
|
+
raise ValueError("Last processor incompatible with exporter output")
|
|
383
354
|
|
|
384
355
|
# Lock the pipeline to prevent further modifications
|
|
385
356
|
self._pipeline_locked = True
|
|
@@ -432,12 +403,15 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
432
403
|
await self.export_processed(processed_item)
|
|
433
404
|
else:
|
|
434
405
|
logger.debug("Skipping export of empty batch")
|
|
435
|
-
elif
|
|
406
|
+
elif self.validate_output_type(processed_item):
|
|
436
407
|
await self.export_processed(processed_item)
|
|
437
408
|
else:
|
|
438
409
|
if raise_on_invalid:
|
|
439
|
-
|
|
440
|
-
|
|
410
|
+
logger.error("Invalid processed item type for export: %s (expected %s or list[%s])",
|
|
411
|
+
type(processed_item),
|
|
412
|
+
self.output_type,
|
|
413
|
+
self.output_type)
|
|
414
|
+
raise ValueError("Invalid processed item type for export")
|
|
441
415
|
logger.warning("Processed item %s is not a valid output type for export", processed_item)
|
|
442
416
|
|
|
443
417
|
async def _continue_pipeline_after(self, source_processor: Processor, item: Any) -> None:
|
|
@@ -512,7 +486,7 @@ class ProcessingExporter(Generic[PipelineInputT, PipelineOutputT], BaseExporter,
|
|
|
512
486
|
event (IntermediateStep): The event to be exported.
|
|
513
487
|
"""
|
|
514
488
|
# Convert IntermediateStep to PipelineInputT and create export task
|
|
515
|
-
if
|
|
489
|
+
if self.validate_input_type(event):
|
|
516
490
|
input_item: PipelineInputT = event # type: ignore
|
|
517
491
|
coro = self._export_with_processing(input_item)
|
|
518
492
|
self._create_export_task(coro)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c)
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -25,9 +25,10 @@ class RedactionConfigMixin(BaseModel):
|
|
|
25
25
|
"""
|
|
26
26
|
redaction_enabled: bool = Field(default=False, description="Whether to enable redaction processing.")
|
|
27
27
|
redaction_value: str = Field(default="[REDACTED]", description="Value to replace redacted attributes with.")
|
|
28
|
-
redaction_attributes: list[str] = Field(default_factory=lambda: ["input.value", "output.value", "metadata"],
|
|
29
|
-
description="
|
|
28
|
+
redaction_attributes: list[str] = Field(default_factory=lambda: ["input.value", "output.value", "nat.metadata"],
|
|
29
|
+
description="Attributes to redact when redaction is triggered.")
|
|
30
30
|
force_redaction: bool = Field(default=False, description="Always redact regardless of other conditions.")
|
|
31
|
+
redaction_tag: str | None = Field(default=None, description="Tag to add to spans when redaction is triggered.")
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class HeaderRedactionConfigMixin(RedactionConfigMixin):
|
|
@@ -38,4 +39,4 @@ class HeaderRedactionConfigMixin(RedactionConfigMixin):
|
|
|
38
39
|
|
|
39
40
|
Note: The callback function must be provided directly to the processor at runtime.
|
|
40
41
|
"""
|
|
41
|
-
|
|
42
|
+
redaction_headers: list[str] = Field(default_factory=list, description="Headers to check for redaction decisions.")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: Copyright (c)
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
#
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
|
+
import sys
|
|
17
|
+
from collections.abc import Mapping
|
|
16
18
|
from enum import Enum
|
|
17
19
|
from typing import Generic
|
|
18
20
|
from typing import TypeVar
|
|
@@ -20,7 +22,17 @@ from typing import TypeVar
|
|
|
20
22
|
from pydantic import BaseModel
|
|
21
23
|
from pydantic import Field
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
if sys.version_info >= (3, 12):
|
|
26
|
+
from typing import TypedDict
|
|
27
|
+
else:
|
|
28
|
+
from typing_extensions import TypedDict
|
|
29
|
+
|
|
30
|
+
TagMappingT = TypeVar("TagMappingT", bound=Mapping)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BaseTaggingConfigMixin(BaseModel, Generic[TagMappingT]):
|
|
34
|
+
"""Base mixin for tagging spans."""
|
|
35
|
+
tags: TagMappingT | None = Field(default=None, description="Tags to add to the span.")
|
|
24
36
|
|
|
25
37
|
|
|
26
38
|
class PrivacyLevel(str, Enum):
|
|
@@ -31,20 +43,20 @@ class PrivacyLevel(str, Enum):
|
|
|
31
43
|
HIGH = "high"
|
|
32
44
|
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
""
|
|
46
|
+
PrivacyTagSchema = TypedDict(
|
|
47
|
+
"PrivacyTagSchema",
|
|
48
|
+
{
|
|
49
|
+
"privacy.level": PrivacyLevel,
|
|
50
|
+
},
|
|
51
|
+
total=True,
|
|
52
|
+
)
|
|
36
53
|
|
|
37
|
-
This mixin provides a flexible tagging system where both the tag key
|
|
38
|
-
and value type can be customized for different use cases.
|
|
39
|
-
"""
|
|
40
|
-
tag_key: str | None = Field(default=None, description="Key to use when tagging traces.")
|
|
41
|
-
tag_value: TagValueT | None = Field(default=None, description="Value to tag the traces with.")
|
|
42
54
|
|
|
55
|
+
class PrivacyTaggingConfigMixin(BaseTaggingConfigMixin[PrivacyTagSchema]):
|
|
56
|
+
"""Mixin for privacy level tagging on spans."""
|
|
57
|
+
pass
|
|
43
58
|
|
|
44
|
-
class PrivacyTaggingConfigMixin(TaggingConfigMixin[PrivacyLevel]):
|
|
45
|
-
"""Mixin for privacy level tagging on spans.
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"""
|
|
60
|
+
class CustomTaggingConfigMixin(BaseTaggingConfigMixin[dict[str, str]]):
|
|
61
|
+
"""Mixin for string key-value tagging on spans."""
|
|
50
62
|
pass
|