nvidia-nat-weave 1.2.0a20250813__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/meta/pypi.md +23 -0
- nat/plugins/weave/__init__.py +0 -0
- nat/plugins/weave/register.py +76 -0
- nat/plugins/weave/weave_exporter.py +233 -0
- nvidia_nat_weave-1.2.0a20250813.dist-info/METADATA +36 -0
- nvidia_nat_weave-1.2.0a20250813.dist-info/RECORD +9 -0
- nvidia_nat_weave-1.2.0a20250813.dist-info/WHEEL +5 -0
- nvidia_nat_weave-1.2.0a20250813.dist-info/entry_points.txt +2 -0
- nvidia_nat_weave-1.2.0a20250813.dist-info/top_level.txt +1 -0
nat/meta/pypi.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
<!--
|
2
|
+
SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
-->
|
17
|
+
|
18
|
+
.
|
File without changes
|
@@ -0,0 +1,76 @@
|
|
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
|
+
|
18
|
+
from pydantic import Field
|
19
|
+
|
20
|
+
from nat.builder.builder import Builder
|
21
|
+
from nat.cli.register_workflow import register_telemetry_exporter
|
22
|
+
from nat.data_models.telemetry_exporter import TelemetryExporterBaseConfig
|
23
|
+
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
|
27
|
+
class WeaveTelemetryExporter(TelemetryExporterBaseConfig, name="weave"):
|
28
|
+
"""A telemetry exporter to transmit traces to Weights & Biases Weave using OpenTelemetry."""
|
29
|
+
project: str = Field(description="The W&B project name.")
|
30
|
+
entity: str | None = Field(default=None, description="The W&B username or team name.")
|
31
|
+
redact_pii: bool = Field(default=False, description="Whether to redact PII from the traces.")
|
32
|
+
redact_pii_fields: list[str] | None = Field(
|
33
|
+
default=None,
|
34
|
+
description="Custom list of PII entity types to redact. Only used when redact_pii=True. "
|
35
|
+
"Examples: CREDIT_CARD, EMAIL_ADDRESS, PHONE_NUMBER, etc.")
|
36
|
+
redact_keys: list[str] | None = Field(
|
37
|
+
default=None,
|
38
|
+
description="Additional keys to redact from traces beyond the default (api_key, auth_headers, authorization).")
|
39
|
+
verbose: bool = Field(default=False, description="Whether to enable verbose logging.")
|
40
|
+
|
41
|
+
|
42
|
+
@register_telemetry_exporter(config_type=WeaveTelemetryExporter)
|
43
|
+
async def weave_telemetry_exporter(config: WeaveTelemetryExporter, builder: Builder): # pylint: disable=unused-argument
|
44
|
+
import weave
|
45
|
+
|
46
|
+
from nat.plugins.weave.weave_exporter import WeaveExporter
|
47
|
+
|
48
|
+
weave_settings = {}
|
49
|
+
|
50
|
+
if config.redact_pii:
|
51
|
+
weave_settings["redact_pii"] = True
|
52
|
+
|
53
|
+
# Add custom fields if specified
|
54
|
+
if config.redact_pii_fields:
|
55
|
+
weave_settings["redact_pii_fields"] = config.redact_pii_fields
|
56
|
+
|
57
|
+
project_name = f"{config.entity}/{config.project}" if config.entity else config.project
|
58
|
+
|
59
|
+
if weave_settings:
|
60
|
+
_ = weave.init(project_name=project_name, settings=weave_settings)
|
61
|
+
else:
|
62
|
+
_ = weave.init(project_name=project_name)
|
63
|
+
|
64
|
+
# Handle custom redact keys if specified
|
65
|
+
if config.redact_keys and config.redact_pii:
|
66
|
+
# Need to create a new list combining default keys and custom ones
|
67
|
+
from weave.trace import sanitize
|
68
|
+
default_keys = sanitize.REDACT_KEYS
|
69
|
+
|
70
|
+
# Create a new list with all keys
|
71
|
+
all_keys = list(default_keys) + config.redact_keys
|
72
|
+
|
73
|
+
# Replace the default REDACT_KEYS with our extended list
|
74
|
+
sanitize.REDACT_KEYS = tuple(all_keys)
|
75
|
+
|
76
|
+
yield WeaveExporter(project=config.project, entity=config.entity, verbose=config.verbose)
|
@@ -0,0 +1,233 @@
|
|
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 Generator
|
18
|
+
from contextlib import contextmanager
|
19
|
+
|
20
|
+
from weave.trace.context import weave_client_context
|
21
|
+
from weave.trace.context.call_context import get_current_call
|
22
|
+
from weave.trace.context.call_context import set_call_stack
|
23
|
+
from weave.trace.weave_client import Call
|
24
|
+
|
25
|
+
from nat.data_models.intermediate_step import IntermediateStep
|
26
|
+
from nat.data_models.span import Span
|
27
|
+
from nat.observability.exporter.base_exporter import IsolatedAttribute
|
28
|
+
from nat.observability.exporter.span_exporter import SpanExporter
|
29
|
+
from nat.utils.log_utils import LogFilter
|
30
|
+
from nat.utils.type_utils import override
|
31
|
+
|
32
|
+
logger = logging.getLogger(__name__)
|
33
|
+
|
34
|
+
# Use LogFilter to filter out specific message patterns
|
35
|
+
presidio_filter = LogFilter([
|
36
|
+
"nlp_engine not provided",
|
37
|
+
"Created NLP engine",
|
38
|
+
"registry not provided",
|
39
|
+
"Loaded recognizer",
|
40
|
+
"Recognizer not added to registry"
|
41
|
+
])
|
42
|
+
|
43
|
+
|
44
|
+
class WeaveExporter(SpanExporter[Span, Span]):
|
45
|
+
"""A Weave exporter that exports telemetry traces to Weights & Biases Weave using OpenTelemetry."""
|
46
|
+
|
47
|
+
_weave_calls: IsolatedAttribute[dict[str, Call]] = IsolatedAttribute(dict)
|
48
|
+
|
49
|
+
def __init__(self,
|
50
|
+
context_state=None,
|
51
|
+
entity: str | None = None,
|
52
|
+
project: str | None = None,
|
53
|
+
verbose: bool = False):
|
54
|
+
super().__init__(context_state=context_state)
|
55
|
+
self._entity = entity
|
56
|
+
self._project = project
|
57
|
+
self._gc = weave_client_context.require_weave_client()
|
58
|
+
|
59
|
+
# Optionally, set log filtering for presidio-analyzer to reduce verbosity
|
60
|
+
if not verbose:
|
61
|
+
presidio_logger = logging.getLogger('presidio-analyzer')
|
62
|
+
presidio_logger.addFilter(presidio_filter)
|
63
|
+
|
64
|
+
@override
|
65
|
+
async def export_processed(self, item: Span | list[Span]) -> None:
|
66
|
+
"""Dummy implementation of export_processed.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
item (Span | list[Span]): The span or list of spans to export.
|
70
|
+
"""
|
71
|
+
pass
|
72
|
+
|
73
|
+
def _process_start_event(self, event: IntermediateStep):
|
74
|
+
"""Process the start event for a Weave call.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
event (IntermediateStep): The intermediate step event.
|
78
|
+
"""
|
79
|
+
super()._process_start_event(event)
|
80
|
+
span = self._span_stack.get(event.UUID, None)
|
81
|
+
if span is None:
|
82
|
+
logger.warning("No span found for event %s", event.UUID)
|
83
|
+
return
|
84
|
+
self._create_weave_call(event, span)
|
85
|
+
|
86
|
+
def _process_end_event(self, event: IntermediateStep):
|
87
|
+
"""Process the end event for a Weave call.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
event (IntermediateStep): The intermediate step event.
|
91
|
+
"""
|
92
|
+
super()._process_end_event(event)
|
93
|
+
self._finish_weave_call(event)
|
94
|
+
|
95
|
+
@contextmanager
|
96
|
+
def parent_call(self, trace_id: str, parent_call_id: str) -> Generator[None]:
|
97
|
+
"""Create a dummy Weave call for the parent span.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
trace_id (str): The trace ID of the parent span.
|
101
|
+
parent_call_id (str): The ID of the parent call.
|
102
|
+
|
103
|
+
Yields:
|
104
|
+
None: The dummy Weave call.
|
105
|
+
"""
|
106
|
+
dummy_call = Call(trace_id=trace_id, id=parent_call_id, _op_name="", project_id="", parent_id=None, inputs={})
|
107
|
+
with set_call_stack([dummy_call]):
|
108
|
+
yield
|
109
|
+
|
110
|
+
def _create_weave_call(self, step: IntermediateStep, span: Span) -> Call:
|
111
|
+
"""
|
112
|
+
Create a Weave call directly from the span and step data,
|
113
|
+
connecting to existing framework traces if available.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
step (IntermediateStep): The intermediate step event.
|
117
|
+
span (Span): The span associated with the intermediate step.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Call: The Weave call created from the span and step data.
|
121
|
+
"""
|
122
|
+
# Check for existing Weave trace/call
|
123
|
+
existing_call = get_current_call()
|
124
|
+
|
125
|
+
# Extract parent call if applicable
|
126
|
+
parent_call = None
|
127
|
+
|
128
|
+
# If we have an existing Weave call from another framework (e.g., LangChain),
|
129
|
+
# use it as the parent
|
130
|
+
if existing_call is not None:
|
131
|
+
parent_call = existing_call
|
132
|
+
logger.debug("Found existing Weave call: %s from trace: %s", existing_call.id, existing_call.trace_id)
|
133
|
+
# Otherwise, check our internal stack for parent relationships
|
134
|
+
elif len(self._weave_calls) > 0 and len(self._span_stack) > 1:
|
135
|
+
# Get the parent span using stack position (one level up)
|
136
|
+
parent_span_id = self._span_stack[-2].context.span_id
|
137
|
+
# Find the corresponding weave call for this parent span
|
138
|
+
for call in self._weave_calls.values():
|
139
|
+
if getattr(call, "span_id", None) == parent_span_id:
|
140
|
+
parent_call = call
|
141
|
+
break
|
142
|
+
|
143
|
+
# Generate a meaningful operation name based on event type
|
144
|
+
event_type = step.payload.event_type.split(".")[-1]
|
145
|
+
if step.payload.name:
|
146
|
+
op_name = f"aiq.{event_type}.{step.payload.name}"
|
147
|
+
else:
|
148
|
+
op_name = f"aiq.{event_type}"
|
149
|
+
|
150
|
+
# Create input dictionary
|
151
|
+
inputs = {}
|
152
|
+
if step.payload.data and step.payload.data.input is not None:
|
153
|
+
try:
|
154
|
+
# Add the input to the Weave call
|
155
|
+
inputs["input"] = step.payload.data.input
|
156
|
+
except Exception:
|
157
|
+
# If serialization fails, use string representation
|
158
|
+
inputs["input"] = str(step.payload.data.input)
|
159
|
+
|
160
|
+
# Create the Weave call
|
161
|
+
call = self._gc.create_call(
|
162
|
+
op_name,
|
163
|
+
inputs=inputs,
|
164
|
+
parent=parent_call,
|
165
|
+
attributes=span.attributes,
|
166
|
+
display_name=op_name,
|
167
|
+
)
|
168
|
+
|
169
|
+
# Store the call with step UUID as key
|
170
|
+
self._weave_calls[step.UUID] = call
|
171
|
+
|
172
|
+
# Store span ID for parent reference
|
173
|
+
if span.context is not None:
|
174
|
+
setattr(call, "span_id", span.context.span_id)
|
175
|
+
else:
|
176
|
+
logger.warning("Span has no context, skipping span_id setting")
|
177
|
+
|
178
|
+
return call
|
179
|
+
|
180
|
+
def _finish_weave_call(self, step: IntermediateStep) -> None:
|
181
|
+
"""
|
182
|
+
Finish a previously created Weave call.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
step (IntermediateStep): The intermediate step event.
|
186
|
+
"""
|
187
|
+
# Find the call for this step
|
188
|
+
call = self._weave_calls.pop(step.UUID, None)
|
189
|
+
|
190
|
+
if call is None:
|
191
|
+
logger.warning("No Weave call found for step %s", step.UUID)
|
192
|
+
return
|
193
|
+
|
194
|
+
# Create output dictionary
|
195
|
+
outputs = {}
|
196
|
+
if step.payload.data and step.payload.data.output is not None:
|
197
|
+
try:
|
198
|
+
# Add the output to the Weave call
|
199
|
+
outputs["output"] = step.payload.data.output
|
200
|
+
except Exception:
|
201
|
+
# If serialization fails, use string representation
|
202
|
+
outputs["output"] = str(step.payload.data.output)
|
203
|
+
|
204
|
+
# Add usage information if available
|
205
|
+
usage_info = step.payload.usage_info
|
206
|
+
if usage_info:
|
207
|
+
if usage_info.token_usage:
|
208
|
+
outputs["prompt_tokens"] = usage_info.token_usage.prompt_tokens
|
209
|
+
outputs["completion_tokens"] = usage_info.token_usage.completion_tokens
|
210
|
+
outputs["total_tokens"] = usage_info.token_usage.total_tokens
|
211
|
+
|
212
|
+
if usage_info.num_llm_calls:
|
213
|
+
outputs["num_llm_calls"] = usage_info.num_llm_calls
|
214
|
+
|
215
|
+
if usage_info.seconds_between_calls:
|
216
|
+
outputs["seconds_between_calls"] = usage_info.seconds_between_calls
|
217
|
+
|
218
|
+
# Finish the call with outputs
|
219
|
+
self._gc.finish_call(call, outputs)
|
220
|
+
|
221
|
+
async def _cleanup_weave_calls(self) -> None:
|
222
|
+
"""
|
223
|
+
Clean up any lingering Weave calls.
|
224
|
+
"""
|
225
|
+
if self._weave_calls:
|
226
|
+
for _, call in list(self._weave_calls.items()):
|
227
|
+
self._gc.finish_call(call, {"status": "incomplete"})
|
228
|
+
self._weave_calls.clear()
|
229
|
+
|
230
|
+
async def _cleanup(self) -> None:
|
231
|
+
"""Perform cleanup once the exporter is stopped."""
|
232
|
+
await self._cleanup_weave_calls()
|
233
|
+
await super()._cleanup()
|
@@ -0,0 +1,36 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: nvidia-nat-weave
|
3
|
+
Version: 1.2.0a20250813
|
4
|
+
Summary: Subpackage for Weave integration in NeMo Agent toolkit
|
5
|
+
Keywords: ai,observability,wandb,pii
|
6
|
+
Classifier: Programming Language :: Python
|
7
|
+
Requires-Python: <3.13,>=3.11
|
8
|
+
Description-Content-Type: text/markdown
|
9
|
+
Requires-Dist: nvidia-nat==v1.2.0a20250813
|
10
|
+
Requires-Dist: presidio-analyzer~=2.2
|
11
|
+
Requires-Dist: presidio-anonymizer~=2.2
|
12
|
+
Requires-Dist: weave~=0.51
|
13
|
+
|
14
|
+
<!--
|
15
|
+
SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
16
|
+
SPDX-License-Identifier: Apache-2.0
|
17
|
+
|
18
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
19
|
+
you may not use this file except in compliance with the License.
|
20
|
+
You may obtain a copy of the License at
|
21
|
+
|
22
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
23
|
+
|
24
|
+
Unless required by applicable law or agreed to in writing, software
|
25
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
26
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
27
|
+
See the License for the specific language governing permissions and
|
28
|
+
limitations under the License.
|
29
|
+
-->
|
30
|
+
|
31
|
+
.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
nat/meta/pypi.md,sha256=cVwHYnvOVN3K-yyZzrMa2pMObusdyEE0DYkxC6R4Dys,1131
|
2
|
+
nat/plugins/weave/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
nat/plugins/weave/register.py,sha256=VSecYad8SJDzlmfqc3y7CvsvtaCs9-yMaMZScJ59-Ck,3216
|
4
|
+
nat/plugins/weave/weave_exporter.py,sha256=NC_q3wc_KKbHTN7vgLEHmbP8PV8HQnwfapnZk-ZzkXI,8789
|
5
|
+
nvidia_nat_weave-1.2.0a20250813.dist-info/METADATA,sha256=0byhN-yGrhSljQaSz69oaFWCjQ7z91SG-2ZWhxAEO_0,1563
|
6
|
+
nvidia_nat_weave-1.2.0a20250813.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
+
nvidia_nat_weave-1.2.0a20250813.dist-info/entry_points.txt,sha256=xg4vW3wKsOLfHa-QR6JqWnq3DmdfI_z9IZfg4I9thTY,56
|
8
|
+
nvidia_nat_weave-1.2.0a20250813.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
|
9
|
+
nvidia_nat_weave-1.2.0a20250813.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
nat
|