Skip to content

Commit 00c7293

Browse files
committed
Harden DD trace benchmark plumbing
Signed-off-by: Aaron Gonzales <aagonzales@nvidia.com>
1 parent 1d88389 commit 00c7293

6 files changed

Lines changed: 196 additions & 167 deletions

File tree

.github/workflows/benchmark-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ on:
2424
type: choice
2525
options:
2626
- "none"
27-
- "last-message"
28-
- "all-messages"
27+
- "last_message"
28+
- "all_messages"
2929
default: "none"
3030
dd_task_trace:
3131
description: "Capture sanitized DataDesigner scheduler task traces"

src/anonymizer/engine/ndd/adapter.py

Lines changed: 168 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,163 @@ class _PrivateFacadeTraceColumn:
7575
column_name: str
7676

7777

78+
@dataclass(frozen=True)
79+
class _DDMessageTracePlan:
80+
columns: list[ColumnConfigT]
81+
native_columns: list[_NativeTraceColumn]
82+
private_columns: list[_PrivateFacadeTraceColumn]
83+
unsupported_columns: list[ColumnConfigT]
84+
85+
@classmethod
86+
def from_columns(
87+
cls,
88+
*,
89+
columns: list[ColumnConfigT],
90+
model_configs: list[ModelConfig],
91+
collector: Any | None,
92+
) -> _DDMessageTracePlan:
93+
if collector is None or not collector.dd_trace_enabled:
94+
return cls(columns=columns, native_columns=[], private_columns=[], unsupported_columns=[])
95+
96+
model_configs_by_alias = {model_config.alias: model_config for model_config in model_configs}
97+
native_columns: list[_NativeTraceColumn] = []
98+
private_columns: list[_PrivateFacadeTraceColumn] = []
99+
unsupported_columns: list[ColumnConfigT] = []
100+
configured_columns: list[ColumnConfigT] = []
101+
102+
for column in columns:
103+
if isinstance(column, _TRACEABLE_LLM_COLUMN_TYPES):
104+
configured_columns.append(
105+
cast(ColumnConfigT, column.model_copy(update={"with_trace": cls.trace_type()}))
106+
)
107+
model_config = model_configs_by_alias.get(column.model_alias)
108+
native_columns.append(
109+
_NativeTraceColumn(
110+
column_name=column.name,
111+
trace_column_name=f"{column.name}{TRACE_COLUMN_POSTFIX}",
112+
model_alias=column.model_alias,
113+
model_name=getattr(model_config, "model", None),
114+
model_provider_name=getattr(model_config, "provider", None),
115+
)
116+
)
117+
continue
118+
119+
if _column_has_private_facade_model_calls(column):
120+
configured_columns.append(_custom_column_with_trace_context(column))
121+
private_columns.append(_PrivateFacadeTraceColumn(column_name=column.name))
122+
continue
123+
124+
unsupported_columns.append(column)
125+
configured_columns.append(column)
126+
127+
return cls(
128+
columns=configured_columns,
129+
native_columns=native_columns,
130+
private_columns=private_columns,
131+
unsupported_columns=unsupported_columns,
132+
)
133+
134+
@staticmethod
135+
def trace_type() -> TraceType:
136+
# Preserve Anonymizer's existing dd_trace=last_message semantics: the trace
137+
# sink records the final prompt message and response separately, while DD's
138+
# native LAST_MESSAGE side effect only keeps the final assistant message.
139+
return TraceType.ALL_MESSAGES
140+
141+
def record_coverage(self, *, workflow_name: str, collector: Any | None) -> None:
142+
if collector is None or not collector.dd_trace_enabled:
143+
return
144+
145+
traced_column_names = [column.column_name for column in self.native_columns] + [
146+
column.column_name for column in self.private_columns
147+
]
148+
collector.record(
149+
"dd_trace_coverage",
150+
workflow_name=workflow_name,
151+
trace_backend=self.backend,
152+
trace_mode=collector.dd_trace_mode,
153+
native_trace_type=self.trace_type().value,
154+
traced_column_count=len(traced_column_names),
155+
traced_column_names=traced_column_names,
156+
native_trace_column_count=len(self.native_columns),
157+
native_trace_column_names=[column.column_name for column in self.native_columns],
158+
private_trace_column_count=len(self.private_columns),
159+
private_trace_column_names=[column.column_name for column in self.private_columns],
160+
private_trace_backend="anonymizer_private_model_facade" if self.private_columns else None,
161+
private_trace_note=(
162+
"temporary private DataDesigner model registry/facade instrumentation" if self.private_columns else None
163+
),
164+
unsupported_column_count=len(self.unsupported_columns),
165+
unsupported_column_names=[column.name for column in self.unsupported_columns],
166+
unsupported_column_types=[_column_type_name(column) for column in self.unsupported_columns],
167+
)
168+
169+
@property
170+
def backend(self) -> str:
171+
if self.native_columns and self.private_columns:
172+
return "mixed"
173+
if self.private_columns:
174+
return "anonymizer_private_model_facade"
175+
return "data_designer_column"
176+
177+
def record_and_strip_native_traces(
178+
self,
179+
*,
180+
output_df: pd.DataFrame,
181+
workflow_name: str,
182+
collector: Any | None,
183+
) -> pd.DataFrame:
184+
if not self.native_columns:
185+
return output_df
186+
187+
trace_column_names = [column.trace_column_name for column in self.native_columns]
188+
if collector is not None and collector.dd_trace_enabled:
189+
for _, row in output_df.iterrows():
190+
for trace_column in self.native_columns:
191+
if trace_column.trace_column_name not in output_df.columns:
192+
continue
193+
self._record_native_trace(
194+
trace_column=trace_column,
195+
trace_value=row.get(trace_column.trace_column_name),
196+
workflow_name=workflow_name,
197+
collector=collector,
198+
)
199+
200+
existing_trace_columns = [column_name for column_name in trace_column_names if column_name in output_df.columns]
201+
if not existing_trace_columns:
202+
return output_df
203+
return output_df.drop(columns=existing_trace_columns)
204+
205+
@staticmethod
206+
def _record_native_trace(
207+
*,
208+
trace_column: _NativeTraceColumn,
209+
trace_value: Any,
210+
workflow_name: str,
211+
collector: Any,
212+
) -> None:
213+
trace_messages = _native_trace_messages(trace_value)
214+
if not trace_messages:
215+
return
216+
collector.record_dd_message_trace(
217+
workflow_name=workflow_name,
218+
trace_source="data_designer_column",
219+
column_name=trace_column.column_name,
220+
trace_column_name=trace_column.trace_column_name,
221+
model_alias=trace_column.model_alias,
222+
model_name=trace_column.model_name,
223+
model_provider_name=trace_column.model_provider_name,
224+
modality="chat",
225+
is_async=None,
226+
status="completed",
227+
error_type=None,
228+
elapsed_sec=None,
229+
messages=_select_native_trace_messages(trace_messages, mode=collector.dd_trace_mode),
230+
response=_native_trace_response(trace_messages),
231+
usage=None,
232+
)
233+
234+
78235
class _TaskTraceLike(Protocol):
79236
column: Any
80237
row_group: Any
@@ -158,25 +315,20 @@ def run_workflow(
158315
)
159316
started = time.perf_counter()
160317
collector = current_collector()
161-
columns, native_trace_columns, private_trace_columns, unsupported_trace_columns = _configure_dd_message_traces(
318+
trace_plan = _DDMessageTracePlan.from_columns(
162319
columns=columns,
163320
model_configs=model_configs,
164321
collector=collector,
165322
)
323+
columns = trace_plan.columns
166324
usage_probe = _DataDesignerUsageProbe(
167325
self._data_designer,
168326
enabled=collector is not None,
169327
collector=collector,
170328
workflow_name=workflow_name,
171-
private_trace_columns=private_trace_columns,
172-
)
173-
_record_dd_trace_coverage(
174-
workflow_name=workflow_name,
175-
collector=collector,
176-
native_trace_columns=native_trace_columns,
177-
private_trace_columns=private_trace_columns,
178-
unsupported_trace_columns=unsupported_trace_columns,
329+
private_trace_columns=trace_plan.private_columns,
179330
)
331+
trace_plan.record_coverage(workflow_name=workflow_name, collector=collector)
180332

181333
with tempfile.TemporaryDirectory(prefix=f"anonymizer_{workflow_name}_") as tmp_dir:
182334
seed_path = str(Path(tmp_dir) / "seed.parquet")
@@ -240,11 +392,10 @@ def run_workflow(
240392
)
241393
raise AnonymizerWorkflowError(f"Workflow failed: {exc}") from exc
242394

243-
output_df = _record_and_strip_native_dd_message_traces(
395+
output_df = trace_plan.record_and_strip_native_traces(
244396
output_df=output_df,
245397
workflow_name=workflow_name,
246398
collector=collector,
247-
native_trace_columns=native_trace_columns,
248399
)
249400
_record_dd_task_traces(
250401
workflow_name=workflow_name,
@@ -816,158 +967,23 @@ def traced_generator(*args: Any, **kwargs: Any) -> Any:
816967
return cast(ColumnConfigT, column.model_copy(update={"generator_function": traced_generator}))
817968

818969

819-
def _configure_dd_message_traces(
820-
*,
821-
columns: list[ColumnConfigT],
822-
model_configs: list[ModelConfig],
823-
collector: Any | None,
824-
) -> tuple[list[ColumnConfigT], list[_NativeTraceColumn], list[_PrivateFacadeTraceColumn], list[ColumnConfigT]]:
825-
if collector is None or not collector.dd_trace_enabled:
826-
return columns, [], [], []
827-
828-
model_configs_by_alias = {model_config.alias: model_config for model_config in model_configs}
829-
native_trace_columns: list[_NativeTraceColumn] = []
830-
private_trace_columns: list[_PrivateFacadeTraceColumn] = []
831-
unsupported_columns: list[ColumnConfigT] = []
832-
configured_columns: list[ColumnConfigT] = []
833-
trace_type = _native_dd_trace_type()
834-
835-
for column in columns:
836-
if isinstance(column, _TRACEABLE_LLM_COLUMN_TYPES):
837-
configured_column = cast(ColumnConfigT, column.model_copy(update={"with_trace": trace_type}))
838-
configured_columns.append(configured_column)
839-
model_config = model_configs_by_alias.get(column.model_alias)
840-
native_trace_columns.append(
841-
_NativeTraceColumn(
842-
column_name=column.name,
843-
trace_column_name=f"{column.name}{TRACE_COLUMN_POSTFIX}",
844-
model_alias=column.model_alias,
845-
model_name=getattr(model_config, "model", None),
846-
model_provider_name=getattr(model_config, "provider", None),
847-
)
848-
)
849-
continue
850-
851-
if _column_has_private_facade_model_calls(column):
852-
configured_columns.append(_custom_column_with_trace_context(column))
853-
private_trace_columns.append(_PrivateFacadeTraceColumn(column_name=column.name))
854-
continue
855-
856-
unsupported_columns.append(column)
857-
configured_columns.append(column)
858-
859-
return configured_columns, native_trace_columns, private_trace_columns, unsupported_columns
860-
861-
862-
def _native_dd_trace_type() -> TraceType:
863-
# Preserve Anonymizer's existing dd_trace=last_message semantics: the trace
864-
# sink records the final prompt message and response separately, while DD's
865-
# native LAST_MESSAGE side effect only keeps the final assistant message.
866-
return TraceType.ALL_MESSAGES
867-
868-
869970
def _column_has_private_facade_model_calls(column: ColumnConfigT) -> TypeGuard[CustomColumnConfig]:
870971
return isinstance(column, CustomColumnConfig) and bool(_extract_workflow_model_aliases([column]))
871972

872973

873-
def _record_dd_trace_coverage(
874-
*,
875-
workflow_name: str,
876-
collector: Any,
877-
native_trace_columns: list[_NativeTraceColumn],
878-
private_trace_columns: list[_PrivateFacadeTraceColumn],
879-
unsupported_trace_columns: list[ColumnConfigT],
880-
) -> Any:
881-
if collector is None or not collector.dd_trace_enabled:
882-
return
883-
traced_column_names = [column.column_name for column in native_trace_columns] + [
884-
column.column_name for column in private_trace_columns
885-
]
886-
collector.record(
887-
"dd_trace_coverage",
888-
workflow_name=workflow_name,
889-
trace_backend=_dd_trace_backend(native_trace_columns, private_trace_columns),
890-
trace_mode=collector.dd_trace_mode,
891-
native_trace_type=_native_dd_trace_type().value,
892-
traced_column_count=len(traced_column_names),
893-
traced_column_names=traced_column_names,
894-
native_trace_column_count=len(native_trace_columns),
895-
native_trace_column_names=[column.column_name for column in native_trace_columns],
896-
private_trace_column_count=len(private_trace_columns),
897-
private_trace_column_names=[column.column_name for column in private_trace_columns],
898-
private_trace_backend="anonymizer_private_model_facade" if private_trace_columns else None,
899-
private_trace_note=(
900-
"temporary private DataDesigner model registry/facade instrumentation" if private_trace_columns else None
901-
),
902-
unsupported_column_count=len(unsupported_trace_columns),
903-
unsupported_column_names=[column.name for column in unsupported_trace_columns],
904-
unsupported_column_types=[_column_type_name(column) for column in unsupported_trace_columns],
905-
)
906-
907-
908-
def _dd_trace_backend(
909-
native_trace_columns: list[_NativeTraceColumn],
910-
private_trace_columns: list[_PrivateFacadeTraceColumn],
911-
) -> str:
912-
if native_trace_columns and private_trace_columns:
913-
return "mixed"
914-
if private_trace_columns:
915-
return "anonymizer_private_model_facade"
916-
return "data_designer_column"
917-
918-
919974
def _column_type_name(column: ColumnConfigT) -> str:
920975
column_type = getattr(column, "column_type", None)
921976
return str(column_type) if column_type is not None else type(column).__name__
922977

923978

924-
def _record_and_strip_native_dd_message_traces(
925-
*,
926-
output_df: pd.DataFrame,
927-
workflow_name: str,
928-
collector: Any,
929-
native_trace_columns: list[_NativeTraceColumn],
930-
) -> pd.DataFrame:
931-
if not native_trace_columns:
932-
return output_df
933-
934-
trace_column_names = [column.trace_column_name for column in native_trace_columns]
935-
if collector is not None and collector.dd_trace_enabled:
936-
for _, row in output_df.iterrows():
937-
for trace_column in native_trace_columns:
938-
if trace_column.trace_column_name not in output_df.columns:
939-
continue
940-
trace_messages = _native_trace_messages(row.get(trace_column.trace_column_name))
941-
if not trace_messages:
942-
continue
943-
collector.record_dd_message_trace(
944-
workflow_name=workflow_name,
945-
trace_source="data_designer_column",
946-
column_name=trace_column.column_name,
947-
trace_column_name=trace_column.trace_column_name,
948-
model_alias=trace_column.model_alias,
949-
model_name=trace_column.model_name,
950-
model_provider_name=trace_column.model_provider_name,
951-
modality="chat",
952-
is_async=None,
953-
status="completed",
954-
error_type=None,
955-
elapsed_sec=None,
956-
messages=_select_native_trace_messages(trace_messages, mode=collector.dd_trace_mode),
957-
response=_native_trace_response(trace_messages),
958-
usage=None,
959-
)
960-
961-
existing_trace_columns = [column_name for column_name in trace_column_names if column_name in output_df.columns]
962-
if not existing_trace_columns:
963-
return output_df
964-
return output_df.drop(columns=existing_trace_columns)
965-
966-
967979
def _native_trace_messages(value: Any) -> list[dict[str, Any]]:
968-
if not isinstance(value, list):
980+
if value is None or isinstance(value, (str, bytes, Mapping)):
981+
return []
982+
try:
983+
messages = list(value)
984+
except TypeError:
969985
return []
970-
return [_trace_message(message) for message in value]
986+
return [_trace_message(message) for message in messages]
971987

972988

973989
def _select_native_trace_messages(messages: list[dict[str, Any]], *, mode: str) -> list[dict[str, Any]]:

0 commit comments

Comments
 (0)