nvidia-nat-weave 1.2.0__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 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
+ ![NVIDIA NeMo Agent Toolkit](https://media.githubusercontent.com/media/NVIDIA/NeMo-Agent-Toolkit/refs/heads/main/docs/source/_static/banner.png "NeMo Agent toolkit banner image")
19
+
20
+ # NVIDIA NeMo Agent Toolkit Subpackage
21
+ This is a subpackage for Weights and Biases Weave integration for observability.
22
+
23
+ For more information about the NVIDIA NeMo Agent toolkit, please visit the [NeMo Agent toolkit GitHub Repo](https://github.com/NVIDIA/NeMo-Agent-Toolkit).
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.0
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.0
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
+ ![NVIDIA NeMo Agent Toolkit](https://media.githubusercontent.com/media/NVIDIA/NeMo-Agent-Toolkit/refs/heads/main/docs/source/_static/banner.png "NeMo Agent toolkit banner image")
32
+
33
+ # NVIDIA NeMo Agent Toolkit Subpackage
34
+ This is a subpackage for Weights and Biases Weave integration for observability.
35
+
36
+ For more information about the NVIDIA NeMo Agent toolkit, please visit the [NeMo Agent toolkit GitHub Repo](https://github.com/NVIDIA/NeMo-Agent-Toolkit).
@@ -0,0 +1,9 @@
1
+ nat/meta/pypi.md,sha256=FVQR5lfZjqZHm4VWMmQJYgZbwJJjrfQZgEqHscDuMR8,1121
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.0.dist-info/METADATA,sha256=yCE3bqmQlg_ufb6N3pPOKisemkqMx03H5tyKA6VirBU,1535
6
+ nvidia_nat_weave-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ nvidia_nat_weave-1.2.0.dist-info/entry_points.txt,sha256=xg4vW3wKsOLfHa-QR6JqWnq3DmdfI_z9IZfg4I9thTY,56
8
+ nvidia_nat_weave-1.2.0.dist-info/top_level.txt,sha256=8-CJ2cP6-f0ZReXe5Hzqp-5pvzzHz-5Ds5H2bGqh1-U,4
9
+ nvidia_nat_weave-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [nat.components]
2
+ nat_weave = nat.plugins.weave.register
@@ -0,0 +1 @@
1
+ nat