@@ -276,17 +276,24 @@ def _serialize_invocation(inv) -> dict[str, Any]:
276276 inv_dict : dict [str , Any ] = {
277277 "invocation_id" : inv .invocation_id ,
278278 }
279- if inv .user_content :
279+ if inv .user_content is not None :
280280 inv_dict ["user_content" ] = inv .user_content .model_dump (exclude_none = True )
281- if inv .final_response :
281+ if inv .final_response is not None :
282282 inv_dict ["final_response" ] = inv .final_response .model_dump (exclude_none = True )
283- if inv .intermediate_data :
283+ if inv .intermediate_data is not None :
284284 inv_dict ["intermediate_data" ] = inv .intermediate_data .model_dump (exclude_none = True )
285- if inv .creation_timestamp :
285+ if inv .creation_timestamp is not None :
286286 inv_dict ["creation_timestamp" ] = inv .creation_timestamp
287287 return _camel_keys (inv_dict )
288288
289289
290+ def _get_format_for_file (path : str , explicit_format : str ) -> str :
291+ """Return the loader format for a single file, auto-detecting from extension."""
292+ if explicit_format :
293+ return explicit_format
294+ return "otlp-json" if path .lower ().endswith (".jsonl" ) else "jaeger-json"
295+
296+
290297@router .post ("/convert" , response_model = StandardResponse [ConvertTracesData ])
291298async def convert_trace_files (
292299 trace_files : list [UploadFile ] = File (...),
@@ -295,62 +302,65 @@ async def convert_trace_files(
295302 """Convert trace files to invocations and metadata without running evaluation."""
296303 temp_dir = tempfile .mkdtemp ()
297304 try :
298- trace_paths = []
299- for trace_file in trace_files :
305+ saved_files : list [ tuple [ str , str ]] = [] # (path, original_filename)
306+ for idx , trace_file in enumerate ( trace_files ) :
300307 if not trace_file .filename :
301308 continue
302309
303- if not (trace_file .filename .endswith (".json" ) or trace_file .filename .endswith (".jsonl" )):
310+ original = trace_file .filename
311+ lower = original .lower ()
312+ if not (lower .endswith (".json" ) or lower .endswith (".jsonl" )):
304313 raise HTTPException (
305314 status_code = 400 ,
306- detail = f"Invalid file extension for { trace_file . filename } . Only .json and .jsonl files are allowed." ,
315+ detail = f"Invalid file extension for { original } . Only .json and .jsonl files are allowed." ,
307316 )
308317
309- trace_path = os .path .join (temp_dir , trace_file .filename )
318+ safe_name = f"{ idx } _{ os .path .basename (original )} "
319+ trace_path = os .path .join (temp_dir , safe_name )
310320 with open (trace_path , "wb" ) as f : # noqa: ASYNC230
311321 content = await trace_file .read ()
312322
313323 if len (content ) > 10 * 1024 * 1024 :
314324 raise HTTPException (
315325 status_code = 400 ,
316- detail = f"File { trace_file . filename } exceeds 10MB limit" ,
326+ detail = f"File { original } exceeds 10MB limit" ,
317327 )
318328
319329 f .write (content )
320- trace_paths .append (trace_path )
330+ saved_files .append (( trace_path , original ) )
321331
322- if not trace_paths :
332+ if not saved_files :
323333 raise HTTPException (status_code = 400 , detail = "No valid trace files provided" )
324334
325- fmt = trace_format
326- if not fmt :
327- if trace_paths [0 ].endswith (".jsonl" ):
328- fmt = "otlp-json"
329- else :
330- fmt = "jaeger-json"
331-
332- loader = get_loader (fmt )
333335 all_traces = []
334336 trace_to_filename : dict [str , str ] = {}
335- for path in trace_paths :
337+ load_warnings : list [str ] = []
338+ for path , original in saved_files :
339+ fmt = _get_format_for_file (path , trace_format )
340+ loader = get_loader (fmt )
336341 try :
337342 traces = loader .load (path )
338- filename = os .path .basename (path )
339343 for t in traces :
340- trace_to_filename [t .trace_id ] = filename
344+ trace_to_filename [t .trace_id ] = original
341345 all_traces .extend (traces )
342346 except Exception as exc :
343- logger .warning (f"Failed to load trace file '{ path } ': { exc } " )
347+ msg = f"Failed to load '{ original } ': { exc } "
348+ logger .warning (msg )
349+ load_warnings .append (msg )
344350
345351 if not all_traces :
346- raise HTTPException (status_code = 400 , detail = "No traces found in uploaded files" )
352+ detail = "No traces found in uploaded files"
353+ if load_warnings :
354+ detail += ". Errors: " + "; " .join (load_warnings )
355+ raise HTTPException (status_code = 400 , detail = detail )
347356
348357 conversion_results = convert_traces (all_traces )
349358 trace_map = {t .trace_id : t for t in all_traces }
350359
351360 entries : list [TraceConversionEntry ] = []
352361 for conv_result in conversion_results :
353362 invocations = [_serialize_invocation (inv ) for inv in conv_result .invocations ]
363+ warnings = list (conv_result .warnings )
354364
355365 trace = trace_map .get (conv_result .trace_id )
356366 meta = TraceConversionMetadata ()
@@ -371,7 +381,7 @@ async def convert_trace_files(
371381 TraceConversionEntry (
372382 trace_id = conv_result .trace_id ,
373383 invocations = invocations ,
374- warnings = conv_result . warnings ,
384+ warnings = warnings ,
375385 metadata = meta ,
376386 )
377387 )
0 commit comments