@@ -111,7 +111,7 @@ def __init__(
111
111
) -> None :
112
112
super (BaseLLMObsWriter , self ).__init__ (interval = interval )
113
113
self ._lock = forksafe .RLock ()
114
- self ._buffer : List [Union [ LLMObsSpanEvent , LLMObsEvaluationMetricEvent ] ] = []
114
+ self ._buffer : List [str ] = []
115
115
self ._buffer_size : int = 0
116
116
self ._timeout : float = timeout
117
117
self ._api_key : str = _api_key or config ._dd_api_key
@@ -148,7 +148,7 @@ def stop(self, timeout=None):
148
148
def on_shutdown (self ):
149
149
self .periodic ()
150
150
151
- def _enqueue (self , event : Union [ LLMObsSpanEvent , LLMObsEvaluationMetricEvent ] , event_size : int ) -> None :
151
+ def _enqueue (self , encoded_event : str , event_size : int ) -> None :
152
152
"""Internal shared logic of enqueuing events to be submitted to LLM Observability."""
153
153
with self ._lock :
154
154
if len (self ._buffer ) >= self .BUFFER_LIMIT :
@@ -160,7 +160,7 @@ def _enqueue(self, event: Union[LLMObsSpanEvent, LLMObsEvaluationMetricEvent], e
160
160
if self ._buffer_size + event_size > EVP_PAYLOAD_SIZE_LIMIT :
161
161
logger .debug ("manually flushing buffer because queueing next event will exceed EVP payload limit" )
162
162
self .periodic ()
163
- self ._buffer .append (event )
163
+ self ._buffer .append (encoded_event )
164
164
self ._buffer_size += event_size
165
165
166
166
def _encode (self , payload , num_events ):
@@ -177,7 +177,7 @@ def periodic(self) -> None:
177
177
with self ._lock :
178
178
if not self ._buffer :
179
179
return
180
- events = self ._buffer
180
+ enc_events = self ._buffer
181
181
self ._buffer = []
182
182
self ._buffer_size = 0
183
183
@@ -188,16 +188,13 @@ def periodic(self) -> None:
188
188
"`LLMObs.enable(api_key=...)` before running your application."
189
189
)
190
190
return
191
- data = self ._data (events )
192
- enc_llm_events = self ._encode (data , len (events ))
193
- if not enc_llm_events :
194
- return
191
+ payload = self ._data (enc_events )
195
192
try :
196
- self ._send_payload_with_retry (enc_llm_events , len (events ))
193
+ self ._send_payload_with_retry (payload , len (enc_events ))
197
194
except Exception :
198
- telemetry .record_dropped_payload (len (events ), event_type = self .EVENT_TYPE , error = "connection_error" )
195
+ telemetry .record_dropped_payload (len (enc_events ), event_type = self .EVENT_TYPE , error = "connection_error" )
199
196
logger .error (
200
- "failed to send %d LLMObs %s events to %s" , len (events ), self .EVENT_TYPE , self ._intake , exc_info = True
197
+ "failed to send %d LLMObs %s events to %s" , len (enc_events ), self .EVENT_TYPE , self ._intake , exc_info = True
201
198
)
202
199
203
200
def _send_payload (self , payload : bytes , num_events : int ):
@@ -263,11 +260,16 @@ class LLMObsEvalMetricWriter(BaseLLMObsWriter):
263
260
ENDPOINT = EVAL_ENDPOINT
264
261
265
262
def enqueue (self , event : LLMObsEvaluationMetricEvent ) -> None :
266
- event_size = len (safe_json (event ))
267
- self ._enqueue (event , event_size )
263
+ encoded_event = self ._encode (event , 1 )
264
+ if not encoded_event :
265
+ return
266
+ event_size = len (encoded_event )
267
+ self ._enqueue (encoded_event , event_size )
268
268
269
- def _data (self , events : List [LLMObsEvaluationMetricEvent ]) -> Dict [str , Any ]:
270
- return {"data" : {"type" : "evaluation_metric" , "attributes" : {"metrics" : events }}}
269
+ def _data (self , events : List [str ]) -> str :
270
+ metrics = "," .join (events )
271
+ enc_payload = f'{{"data": {{"type": "evaluation_metric", "attributes": {{"metrics": [{ metrics } ]}}}}}}'
272
+ return enc_payload
271
273
272
274
273
275
class LLMObsSpanWriter (BaseLLMObsWriter ):
@@ -279,7 +281,10 @@ class LLMObsSpanWriter(BaseLLMObsWriter):
279
281
ENDPOINT = SPAN_ENDPOINT
280
282
281
283
def enqueue (self , event : LLMObsSpanEvent ) -> None :
282
- raw_event_size = len (safe_json (event ))
284
+ encoded_event = self ._encode (event , 1 )
285
+ if not encoded_event :
286
+ return
287
+ raw_event_size = len (encoded_event )
283
288
truncated_event_size = None
284
289
should_truncate = raw_event_size >= EVP_EVENT_SIZE_LIMIT
285
290
if should_truncate :
@@ -288,16 +293,20 @@ def enqueue(self, event: LLMObsSpanEvent) -> None:
288
293
raw_event_size ,
289
294
)
290
295
event = _truncate_span_event (event )
291
- truncated_event_size = len (safe_json (event ))
296
+ encoded_event = self ._encode (event , 1 )
297
+ truncated_event_size = len (encoded_event )
292
298
telemetry .record_span_event_raw_size (event , raw_event_size )
293
299
telemetry .record_span_event_size (event , truncated_event_size or raw_event_size )
294
- self ._enqueue (event , truncated_event_size or raw_event_size )
295
-
296
- def _data (self , events : List [LLMObsSpanEvent ]) -> List [Dict [str , Any ]]:
297
- return [
298
- {"_dd.stage" : "raw" , "_dd.tracer_version" : ddtrace .__version__ , "event_type" : "span" , "spans" : [event ]}
299
- for event in events
300
- ]
300
+ self ._enqueue (encoded_event , truncated_event_size or raw_event_size )
301
+
302
+ def _data (self , events : List [str ]) -> str :
303
+ payload = []
304
+ tracer_version = ddtrace .__version__
305
+ for event in events :
306
+ payload .append (
307
+ f'{{"_dd.stage": "raw", "_dd.tracer_version": "{ tracer_version } ", "event_type": "span", "spans": [{ event } ]}}'
308
+ )
309
+ return "[{}]" .format ("," .join (payload ))
301
310
302
311
303
312
def _truncate_span_event (event : LLMObsSpanEvent ) -> LLMObsSpanEvent :
0 commit comments