Skip to content

feat(obs): structure-preserving truncation for captured prompt/response content - #747

Merged
jarvis9443 merged 2 commits into
mainfrom
feat/sls-structured-truncation
Jul 10, 2026
Merged

feat(obs): structure-preserving truncation for captured prompt/response content#747
jarvis9443 merged 2 commits into
mainfrom
feat/sls-structured-truncation

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

Problem

When a captured prompt/response exceeds an exporter's content_max_bytes, the cut was a plain UTF-8-boundary byte slice. The chat prompt is the serialized request JSON, so an over-cap payload landed in SLS as broken JSON — half an object, a dangling string escape — and the console can no longer show field hierarchy, array contents, or where the problem sits.

Change

Oversized content that parses as JSON is now reduced structurally, so the logged field stays valid JSON:

  • long string values keep a prefix plus an inline ...[aisix: truncated, N bytes total] marker inside the value;
  • data:<mime>;base64, payloads collapse to [aisix: base64 data uri omitted, N bytes] (detection is exact; bare base64 is indistinguishable from long opaque tokens and keeps the generic prefix+marker);
  • long arrays keep head+tail samples around an explicit {"_aisix_truncated": true, "omitted_items": N} placeholder element;
  • objects keep every key, so the shape stays navigable.

Reduction walks a fixed cap ladder (per-string cap × array head/tail keeps) until the serialized result fits content_max_bytes. Non-JSON text keeps the existing UTF-8-safe byte cut, and JSON that cannot fit even at the tightest rung falls back to it too — the byte cap is always honored. The record-level content_truncated flag is unchanged.

Only the prompt/response content fields are affected; metadata fields are untouched. The logic lives in the shared capture layer (SinkContent::capture / CapturedContent::new), so the same guarantee holds at both capture stages (handler-side largest cap → per-exporter re-truncate; the composition is covered by a test) and for the OTLP/Datadog gen_ai.* content attributes, not just SLS.

Notes

  • Runs only when content actually exceeds the cap — the hot path for normal requests is unchanged.
  • Re-serialization orders object keys alphabetically (serde_json map ordering); accepted for over-cap payloads in exchange for intact structure.
  • Baseline: mainstream gateways/proxies truncate logged payloads with blunt prefix cuts plus a marker (some add head+tail sampling for DB rows); none preserve JSON validity. We go further because our captured field is serialized JSON rendered in a log console — validity is what makes it readable there.

Tests

  • 11 unit tests covering the acceptance scenarios from the issue: large nested object, large array (placeholder accounts for every omitted element), oversized string field, multibyte UTF-8, non-JSON text, barely-over-cap boundary, impossible-cap fallback, data-URI placeholder, two-stage cap composition.
  • New e2e sls-structured-truncation-e2e.test.ts: real aisix + etcd + aliyun_sls exporter (content_mode=full, 2 KiB cap) against a mock SLS endpoint; decodes the delivered protobuf and JSON.parses the prompt field back, asserting head/tail sentinels survive, the placeholder accounts for all omitted messages, and the cap is honored.
  • Neighboring content-capture e2es (aliyun-sls-content-capture, otlp-knobs, masked, nontext) pass unchanged.

Fixes api7/AISIX-Cloud#1014

🤖 Generated with Claude Code

…se content

Captured content is usually serialized JSON (the chat prompt is the
serialized request), so the old blunt byte cut at content_max_bytes
left invalid JSON behind — a log console can't render field hierarchy
of a large payload, which is exactly the complaint in
AISIX-Cloud#1014.

Oversized content that parses as JSON is now reduced structurally so
the logged field stays valid JSON:
- long strings keep a prefix + inline '...[aisix: truncated, N bytes
  total]' marker inside the value;
- base64 data URIs collapse to a compact placeholder (exact detection
  only — bare base64 is indistinguishable from opaque tokens and keeps
  the generic prefix+marker);
- long arrays keep head+tail samples around an
  {"_aisix_truncated": true, "omitted_items": N} placeholder;
- objects keep every key.

Reduction walks a cap ladder until the result fits; non-JSON content
and JSON that cannot fit even at the tightest rung fall back to the
historical UTF-8-safe byte cut, so the byte cap is always honored.
Applies at both capture stages (handler-side largest cap, per-exporter
re-truncate), which also means the OTLP/Datadog gen_ai.* content
attributes get the same treatment. Re-serialization orders object keys
alphabetically; accepted for over-cap payloads only.

Runs only when content exceeds the cap, so the hot path for normal
requests is unchanged.

Fixes api7/AISIX-Cloud#1014
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02cfebd3-2dd8-4eb8-80b8-ecae276689f2

📥 Commits

Reviewing files that changed from the base of the PR and between 4ddd6ad and bd1744d.

📒 Files selected for processing (4)
  • crates/aisix-obs/src/sink/mod.rs
  • crates/aisix-obs/src/sink/record.rs
  • crates/aisix-obs/src/sink/truncate.rs
  • tests/e2e/src/cases/sls-structured-truncation-e2e.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sls-structured-truncation

Comment @coderabbitai help to get the list of available commands.

… skip no-op rungs

Audit findings on the initial revision:
- two-stage capture->exporter truncation dropped the stage-1 array
  placeholder into the sampled-out middle, so the stage-2 accounting
  reported only the surviving elements. A dropped placeholder now folds
  its omitted_items count into the new placeholder (composition test
  asserts kept+omitted == original across both stages).
- structurally irreducible payloads paid all ladder rungs for nothing;
  a one-pass measure (largest string / largest array) now skips rungs
  that cannot change anything, and whitespace-heavy input that fits
  once compacted returns early.
- a string barely over the per-string cap could come out LONGER with
  the marker appended; the original is kept when the marker form isn't
  strictly shorter.
@jarvis9443
jarvis9443 merged commit 1888922 into main Jul 10, 2026
12 checks passed
@jarvis9443
jarvis9443 deleted the feat/sls-structured-truncation branch July 10, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant