Skip to content

Commit 1198d10

Browse files
localai-botmudler
andauthored
fix(traces): cap backend trace Data to keep admin UI responsive (#9960)
* fix(traces): cap backend trace Data field so the admin UI stays responsive The previous fix (#9946) capped API trace bodies but missed backend traces, which carry the same blast radius: - LLM backend traces store the full chat messages JSON, full response, and full streaming deltas. Every agent-pool reasoning step ships the full RAG-augmented history (50-500 KiB per trace, often 100+ traces queued). - TTS / audio_transform / transcript traces embed a 30s audio snippet as base64, around 1.3 MiB per trace. Both blow the /api/backend-traces JSON past tens of MiB. The admin Traces page then keeps re-downloading and re-parsing the buffer faster than the 5s auto-refresh and stays in the loading state forever, the same symptom the API-side fix addressed. Apply two complementary caps, both honoring LOCALAI_TRACING_MAX_BODY_BYTES: Option A (safety net in core/trace): RecordBackendTrace walks the Data map recursively and replaces any string value larger than the cap with "<truncated: N bytes>". Catches anything a future producer forgets. Option B (head-preserving at the producer): - core/backend/llm.go: TruncateToBytes on messages, response, and chat_deltas content/reasoning_content so the leading content stays readable in the UI. - core/trace/audio_snippet.go: omit audio_wav_base64 when the encoded blob would exceed the cap (truncated base64 is undecodable). The quality metrics still ship and the UI's WaveformPlayer simply skips when the field is absent. TruncateToBytes is bounded to <= maxBytes so Option A leaves the producer's head-preserving output alone instead of replacing it with the bare marker. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-7 * fix(react-ui): expose tracing_max_body_bytes in Settings and Traces panels The setting was already plumbed through env (LOCALAI_TRACING_MAX_BODY_BYTES), CLI flag, and the runtime_settings.json GET/PUT schema, but neither the main Settings page nor the inline Traces panel offered an input for it. Admins hitting the "Traces UI stuck loading" symptom had to know to set an env var or PUT raw JSON to /api/settings to dial the cap. Add a "Max Body Bytes" row next to "Max Items" in both places. Same input type, same disabled-when-tracing-off semantics, placeholder shows the 65536 default so users see what they're inheriting. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-7 * test(react-ui): disambiguate Max Items locator after adding Max Body Bytes The Tracing settings panel now has two number inputs. The previous spec matched 'input[type="number"]' which became ambiguous and triggered a Playwright strict-mode violation in CI. Switch to getByPlaceholder('100') for Max Items and add a parallel spec for the new Max Body Bytes field using getByPlaceholder('65536'). Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-7 --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
1 parent a0f3e26 commit 1198d10

25 files changed

Lines changed: 368 additions & 35 deletions

core/backend/audio_transform.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func ModelAudioTransform(
7878

7979
var startTime time.Time
8080
if appConfig.EnableTracing {
81-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
81+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
8282
startTime = time.Now()
8383
}
8484

@@ -104,7 +104,7 @@ func ModelAudioTransform(
104104
data["sample_rate"] = res.SampleRate
105105
data["samples"] = res.Samples
106106
data["reference_provided"] = res.ReferenceProvided
107-
if snippet := trace.AudioSnippet(dst); snippet != nil {
107+
if snippet := trace.AudioSnippet(dst, appConfig.TracingMaxBodyBytes); snippet != nil {
108108
maps.Copy(data, snippet)
109109
}
110110
}

core/backend/detection.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func Detection(
3535

3636
var startTime time.Time
3737
if appConfig.EnableTracing {
38-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
38+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
3939
startTime = time.Now()
4040
}
4141

core/backend/embeddings.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func ModelEmbedding(s string, tokens []int, loader *model.ModelLoader, modelConf
6767
}
6868

6969
if appConfig.EnableTracing {
70-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
70+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
7171

7272
traceData := map[string]any{
7373
"input_text": trace.TruncateString(s, 1000),

core/backend/face_analyze.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func FaceAnalyze(
3232

3333
var startTime time.Time
3434
if appConfig.EnableTracing {
35-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
35+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
3636
startTime = time.Now()
3737
}
3838

core/backend/face_verify.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func FaceVerify(
3232

3333
var startTime time.Time
3434
if appConfig.EnableTracing {
35-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
35+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
3636
startTime = time.Now()
3737
}
3838

core/backend/image.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func ImageGeneration(height, width, step, seed int, positive_prompt, negative_pr
4141
}
4242

4343
if appConfig.EnableTracing {
44-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
44+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
4545

4646
traceData := map[string]any{
4747
"positive_prompt": positive_prompt,

core/backend/llm.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ func ModelInference(ctx context.Context, s string, messages schema.Messages, ima
305305
}
306306

307307
if o.EnableTracing {
308-
trace.InitBackendTracingIfEnabled(o.TracingMaxItems)
308+
trace.InitBackendTracingIfEnabled(o.TracingMaxItems, o.TracingMaxBodyBytes)
309309

310310
traceData := map[string]any{
311311
"chat_template": c.TemplateConfig.Chat,
@@ -316,9 +316,13 @@ func ModelInference(ctx context.Context, s string, messages schema.Messages, ima
316316
"audios_count": len(audios),
317317
}
318318

319+
// Cap the captured fields up front: agent-pool LLM calls embed the
320+
// full augmented chat history in messages and the full reply in
321+
// response, so without a per-field cap a single trace can dwarf the
322+
// rest of the buffer. The cap matches the API-trace body cap.
319323
if len(messages) > 0 {
320324
if msgJSON, err := json.Marshal(messages); err == nil {
321-
traceData["messages"] = string(msgJSON)
325+
traceData["messages"] = trace.TruncateToBytes(string(msgJSON), o.TracingMaxBodyBytes)
322326
}
323327
}
324328
if reasoningJSON, err := json.Marshal(c.ReasoningConfig); err == nil {
@@ -337,7 +341,7 @@ func ModelInference(ctx context.Context, s string, messages schema.Messages, ima
337341
resp, err := originalFn()
338342
duration := time.Since(startTime)
339343

340-
traceData["response"] = resp.Response
344+
traceData["response"] = trace.TruncateToBytes(resp.Response, o.TracingMaxBodyBytes)
341345
traceData["token_usage"] = map[string]any{
342346
"prompt": resp.Usage.Prompt,
343347
"completion": resp.Usage.Completion,
@@ -359,10 +363,10 @@ func ModelInference(ctx context.Context, s string, messages schema.Messages, ima
359363
toolCallCount += len(d.ToolCalls)
360364
}
361365
if len(contentParts) > 0 {
362-
chatDeltasInfo["content"] = strings.Join(contentParts, "")
366+
chatDeltasInfo["content"] = trace.TruncateToBytes(strings.Join(contentParts, ""), o.TracingMaxBodyBytes)
363367
}
364368
if len(reasoningParts) > 0 {
365-
chatDeltasInfo["reasoning_content"] = strings.Join(reasoningParts, "")
369+
chatDeltasInfo["reasoning_content"] = trace.TruncateToBytes(strings.Join(reasoningParts, ""), o.TracingMaxBodyBytes)
366370
}
367371
if toolCallCount > 0 {
368372
chatDeltasInfo["tool_call_count"] = toolCallCount

core/backend/options.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func recordModelLoadFailure(appConfig *config.ApplicationConfig, modelName, back
2121
if !appConfig.EnableTracing {
2222
return
2323
}
24-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
24+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
2525
trace.RecordBackendTrace(trace.BackendTrace{
2626
Timestamp: time.Now(),
2727
Type: trace.BackendTraceModelLoad,

core/backend/rerank.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func Rerank(ctx context.Context, request *proto.RerankRequest, loader *model.Mod
2525

2626
var startTime time.Time
2727
if appConfig.EnableTracing {
28-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
28+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
2929
startTime = time.Now()
3030
}
3131

core/backend/soundgeneration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func SoundGeneration(
9898

9999
var startTime time.Time
100100
if appConfig.EnableTracing {
101-
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems)
101+
trace.InitBackendTracingIfEnabled(appConfig.TracingMaxItems, appConfig.TracingMaxBodyBytes)
102102
startTime = time.Now()
103103
}
104104

0 commit comments

Comments
 (0)