The Web UI's offline evaluation flow appears to mishandle OTLP JSON traces exported as .json/.jsonl.
A Tempo/OTLP trace with top-level resourceSpans works in parts of the app only after renaming from .json to .jsonl, but offline evaluation/upload still has UI-side assumptions that appear to either reject .jsonl or crash client-side with:
Cannot read properties of undefined (reading 'map')
No useful backend error is emitted in this failure mode.
Environment
OTLP HTTP: http://0.0.0.0:4318 (OTEL_EXPORTER_OTLP_ENDPOINT default)
OTLP gRPC: 0.0.0.0:4317 (OTEL_EXPORTER_OTLP_PROTOCOL=grpc)
WebSocket: ws://0.0.0.0:8001/ws/traces
API: http://0.0.0.0:8001/api
Web UI: http://localhost:5173
- Trace source: Tempo v2 / OTLP JSON export
- Trace shape: top-level
resourceSpans
Reproduction
- Export an OTLP trace JSON from Tempo or another OTLP source.
- The file has top-level shape:
{
"resourceSpans": [...]
}
- Try uploading as
trace.otlp.json.
Observed:
Failed to load 'trace.otlp.json': Invalid Jaeger JSON format: expected top-level 'data' key
Server log excerpt:
WARNING:agentevals.api.routes:Failed to load 'trace.otlp.json': Invalid Jaeger JSON format: expected top-level 'data' key in /var/folders/nh/5ff88n4577sfrrqrsf71mkkc0000gp/T/tmpz1mwl3cp/0_trace.otlp.json
INFO: 127.0.0.1:59218 - "POST /api/convert HTTP/1.1" 400 Bad Request
WARNING:agentevals.api.routes:Failed to load 'trace.otlp.json': Invalid Jaeger JSON format: expected top-level 'data' key in /var/folders/nh/5ff88n4577sfrrqrsf71mkkc0000gp/T/tmprk1fftge/0_trace.otlp.json
INFO: 127.0.0.1:59237 - "POST /api/convert HTTP/1.1" 400 Bad Request
WARNING:agentevals.api.routes:Failed to load 'trace.otlp.json': Invalid Jaeger JSON format: expected top-level 'data' key in /var/folders/nh/5ff88n4577sfrrqrsf71mkkc0000gp/T/tmp98dia95n/0_trace.otlp.json
INFO: 127.0.0.1:59277 - "POST /api/convert HTTP/1.1" 400 Bad Request
This suggests the upload path defaults .json to Jaeger even when the content is clearly OTLP.
- Rename the same file to:
- The trace now works in at least one path because
.jsonl selects otlp-json.
Observed successful backend conversion after renaming:
INFO:agentevals.loader.otlp:Loaded 1 trace(s) from /var/folders/nh/5ff88n4577sfrrqrsf71mkkc0000gp/T/tmp5pzdcsg0/0_trace.otlp.jsonl
INFO:agentevals.converter:Auto-detected trace format: adk for trace mdQk80xJsNjJzZnIYcvllg==
INFO: 127.0.0.1:59942 - "POST /api/convert HTTP/1.1" 200 OK
- Try using the same
.jsonl trace in the offline evaluation UI.
Observed:
Cannot read properties of undefined (reading 'map')
In my run, there was no corresponding useful backend error, suggesting the failure is frontend-side.
One UI path also appeared to mutate the filename in a way that caused it to be treated as Jaeger again:
WARNING:agentevals.api.routes:Failed to load 'trace.otlp.jsonl.json': Invalid Jaeger JSON format: expected top-level 'data' key in /var/folders/nh/5ff88n4577sfrrqrsf71mkkc0000gp/T/tmpxucohlbs/0_trace.otlp.jsonl.json
INFO: 127.0.0.1:59831 - "POST /api/convert HTTP/1.1" 400 Bad Request
Expected behavior
The UI should support OTLP trace uploads consistently across conversion, inspection, and offline evaluation.
At minimum:
- Trace file upload should accept both
.json and .jsonl.
- Full OTLP JSON files with top-level
resourceSpans should be detected as otlp-json, even when the extension is .json.
- Offline evaluation should pass
trace_format: "otlp-json" when the selected trace file is .jsonl or when content detection identifies OTLP.
- The UI should not crash if the streaming evaluation result shape is missing
traceResults; it should show a useful error instead.
Actual behavior
There appear to be several inconsistent assumptions:
.json uploads are treated as Jaeger JSON and fail on OTLP payloads with top-level resourceSpans.
- Renaming to
.jsonl works around backend format detection.
- Offline evaluation UI appears to accept only
.json in at least one upload/dropzone path, or otherwise does not handle .jsonl cleanly.
- Frontend crashes with:
Cannot read properties of undefined (reading 'map')
Relevant code pointers
FileDropZone defaults accept = '.json', which may block or discourage .jsonl trace files in UI upload paths.
convertTraces(traceFiles, traceFormat?) supports an optional traceFormat, but TraceProvider.setTraceFiles calls convertTraces(files) without passing one.
runEvaluation calls evaluateTracesStreaming(...) with config containing metrics, judge model, threshold, and trajectory match type, but does not include trace_format.
- The backend accepts
.json and .jsonl trace files and infers .jsonl as otlp-json; eval set files are correctly restricted to .json.
TraceProvider completion handling does result.traceResults.map(...) without guarding for missing/alternate result shape, which likely explains the frontend crash.
Suggested fix
-
Add trace-format detection in the frontend or backend based on content:
- top-level
resourceSpans => otlp-json
- top-level
data => jaeger-json
- Tempo v2 wrapper
{ "trace": { "resourceSpans": [...] } } could either be supported or emit a targeted error suggesting jq '.trace // .'.
-
Update trace file upload dropzones to accept:
while keeping eval-set upload restricted to .json.
-
Ensure offline evaluation passes trace_format: "otlp-json" when appropriate.
-
Add a defensive guard around:
result.traceResults.map(...)
for example:
const traceResults =
result.traceResults ??
(result as any).trace_results ??
[];
if (!Array.isArray(traceResults)) {
// surface a useful UI error instead of crashing
}
-
Add regression tests for:
.json OTLP file with top-level resourceSpans
.jsonl OTLP file containing full OTLP JSON, not necessarily newline-delimited individual spans
- offline evaluation UI flow with an OTLP trace
- malformed/partial SSE completion payload that lacks
traceResults
Workaround
For CLI/API usage, explicitly pass OTLP format:
agentevals run trace.otlp.jsonl \
--format otlp-json \
--eval-set eval_set.json \
-m response_match_score
or call the API with:
{
"trace_format": "otlp-json"
}
For the UI, renaming trace.otlp.json to trace.otlp.jsonl works in some paths but not consistently in offline evaluation.
Human confirmation
The Web UI's offline evaluation flow appears to mishandle OTLP JSON traces exported as
.json/.jsonl.A Tempo/OTLP trace with top-level
resourceSpansworks in parts of the app only after renaming from.jsonto.jsonl, but offline evaluation/upload still has UI-side assumptions that appear to either reject.jsonlor crash client-side with:No useful backend error is emitted in this failure mode.
Environment
resourceSpansReproduction
{ "resourceSpans": [...] }trace.otlp.json.Observed:
Server log excerpt:
This suggests the upload path defaults
.jsonto Jaeger even when the content is clearly OTLP..jsonlselectsotlp-json.Observed successful backend conversion after renaming:
.jsonltrace in the offline evaluation UI.Observed:
In my run, there was no corresponding useful backend error, suggesting the failure is frontend-side.
One UI path also appeared to mutate the filename in a way that caused it to be treated as Jaeger again:
Expected behavior
The UI should support OTLP trace uploads consistently across conversion, inspection, and offline evaluation.
At minimum:
.jsonand.jsonl.resourceSpansshould be detected asotlp-json, even when the extension is.json.trace_format: "otlp-json"when the selected trace file is.jsonlor when content detection identifies OTLP.traceResults; it should show a useful error instead.Actual behavior
There appear to be several inconsistent assumptions:
.jsonuploads are treated as Jaeger JSON and fail on OTLP payloads with top-levelresourceSpans..jsonlworks around backend format detection..jsonin at least one upload/dropzone path, or otherwise does not handle.jsonlcleanly.Relevant code pointers
FileDropZonedefaultsaccept = '.json', which may block or discourage.jsonltrace files in UI upload paths.convertTraces(traceFiles, traceFormat?)supports an optionaltraceFormat, butTraceProvider.setTraceFilescallsconvertTraces(files)without passing one.runEvaluationcallsevaluateTracesStreaming(...)with config containing metrics, judge model, threshold, and trajectory match type, but does not includetrace_format..jsonand.jsonltrace files and infers.jsonlasotlp-json; eval set files are correctly restricted to.json.TraceProvidercompletion handling doesresult.traceResults.map(...)without guarding for missing/alternate result shape, which likely explains the frontend crash.Suggested fix
Add trace-format detection in the frontend or backend based on content:
resourceSpans=>otlp-jsondata=>jaeger-json{ "trace": { "resourceSpans": [...] } }could either be supported or emit a targeted error suggestingjq '.trace // .'.Update trace file upload dropzones to accept:
while keeping eval-set upload restricted to
.json.Ensure offline evaluation passes
trace_format: "otlp-json"when appropriate.Add a defensive guard around:
for example:
Add regression tests for:
.jsonOTLP file with top-levelresourceSpans.jsonlOTLP file containing full OTLP JSON, not necessarily newline-delimited individual spanstraceResultsWorkaround
For CLI/API usage, explicitly pass OTLP format:
or call the API with:
{ "trace_format": "otlp-json" }For the UI, renaming
trace.otlp.jsontotrace.otlp.jsonlworks in some paths but not consistently in offline evaluation.Human confirmation