1616import logging
1717from collections .abc import Generator
1818from contextlib import contextmanager
19+ from typing import Any
1920
2021from nat .data_models .intermediate_step import IntermediateStep
2122from nat .data_models .span import Span
2223from nat .observability .exporter .base_exporter import IsolatedAttribute
2324from nat .observability .exporter .span_exporter import SpanExporter
2425from nat .utils .log_utils import LogFilter
26+ from nat .utils .string_utils import truncate_string
2527from nat .utils .type_utils import override
2628from weave .trace .context import weave_client_context
2729from weave .trace .context .call_context import get_current_call
@@ -152,6 +154,7 @@ def _create_weave_call(self, step: IntermediateStep, span: Span) -> Call:
152154 try :
153155 # Add the input to the Weave call
154156 inputs ["input" ] = step .payload .data .input
157+ self ._extract_input_message (step .payload .data .input , inputs )
155158 except Exception :
156159 # If serialization fails, use string representation
157160 inputs ["input" ] = str (step .payload .data .input )
@@ -176,6 +179,76 @@ def _create_weave_call(self, step: IntermediateStep, span: Span) -> Call:
176179
177180 return call
178181
182+ def _extract_input_message (self , input_data : Any , inputs : dict [str , Any ]) -> None :
183+ """
184+ Extract message content from input data and add to inputs dictionary.
185+ Also handles websocket mode where message is located at messages[0].content[0].text.
186+
187+ Args:
188+ input_data: The raw input data from the request
189+ inputs: Dictionary to populate with extracted message content
190+ """
191+ # Extract message content if input has messages attribute
192+ messages = getattr (input_data , 'messages' , [])
193+ if messages :
194+ content = messages [0 ].content
195+ if isinstance (content , list ) and content :
196+ inputs ["input_message" ] = getattr (content [0 ], 'text' , content [0 ])
197+ else :
198+ inputs ["input_message" ] = content
199+
200+ def _extract_output_message (self , output_data : Any , outputs : dict [str , Any ]) -> None :
201+ """
202+ Extract message content from various response formats and add a preview to the outputs dictionary.
203+ No data is added to the outputs dictionary if the output format is not supported.
204+
205+ Supported output formats for message content include:
206+
207+ - output.choices[0].message.content /chat endpoint
208+ - output.value /generate endpoint
209+ - output[0].choices[0].message.content chat WS schema
210+ - output[0].choices[0].delta.content chat_stream WS schema, /chat/stream endpoint
211+ - output[0].value generate & generate_stream WS schema, /generate/stream endpoint
212+
213+ Args:
214+ output_data: The raw output data from the response
215+ outputs: Dictionary to populate with extracted message content.
216+ """
217+ # Handle choices-keyed output object for /chat completion endpoint
218+ choices = getattr (output_data , 'choices' , None )
219+ if choices :
220+ outputs ["output_message" ] = truncate_string (choices [0 ].message .content )
221+ return
222+
223+ # Handle value-keyed output object for union types common for /generate completion endpoint
224+ value = getattr (output_data , 'value' , None )
225+ if value :
226+ outputs ["output_message" ] = truncate_string (value )
227+ return
228+
229+ # Handle list-based outputs (streaming or websocket)
230+ if not isinstance (output_data , list ) or not output_data :
231+ return
232+
233+ choices = getattr (output_data [0 ], 'choices' , None )
234+ if choices :
235+ # chat websocket schema
236+ message = getattr (choices [0 ], 'message' , None )
237+ if message :
238+ outputs ["output_message" ] = truncate_string (getattr (message , 'content' , None ))
239+ return
240+
241+ # chat_stream websocket schema and /chat/stream completion endpoint
242+ delta = getattr (choices [0 ], 'delta' , None )
243+ if delta :
244+ outputs ["output_preview" ] = truncate_string (getattr (delta , 'content' , None ))
245+ return
246+
247+ # generate & generate_stream websocket schema, and /generate/stream completion endpoint
248+ value = getattr (output_data [0 ], 'value' , None )
249+ if value :
250+ outputs ["output_preview" ] = truncate_string (str (value ))
251+
179252 def _finish_weave_call (self , step : IntermediateStep ) -> None :
180253 """
181254 Finish a previously created Weave call.
@@ -196,6 +269,7 @@ def _finish_weave_call(self, step: IntermediateStep) -> None:
196269 try :
197270 # Add the output to the Weave call
198271 outputs ["output" ] = step .payload .data .output
272+ self ._extract_output_message (step .payload .data .output , outputs )
199273 except Exception :
200274 # If serialization fails, use string representation
201275 outputs ["output" ] = str (step .payload .data .output )
0 commit comments