@@ -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+
78235class _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-
869970def _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-
919974def _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-
967979def _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
973989def _select_native_trace_messages (messages : list [dict [str , Any ]], * , mode : str ) -> list [dict [str , Any ]]:
0 commit comments