Skip to content

feat(obs): content capture for embeddings/rerank/images/audio (LiteLLM parity) - #708

Merged
jarvis9443 merged 2 commits into
mainfrom
fix/issue-700-content-capture
Jul 2, 2026
Merged

feat(obs): content capture for embeddings/rerank/images/audio (LiteLLM parity)#708
jarvis9443 merged 2 commits into
mainfrom
fix/issue-700-content-capture

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Problem

Audit finding #700 (parent: api7/AISIX-Cloud#950), completing the AISIX-Cloud#947 family: embeddings / rerank / images / audio fanned observability events out with content: None, so a content_mode = full exporter received metadata only for their traffic.

Scope decision

Per the user's call on #700, the capture scope follows LiteLLM's standard logging payload (verified against LiteLLM HEAD 88e03e54):

endpoint prompt (captured) response (captured)
embeddings post-redaction request JSON full embeddings JSON, vectors included (LiteLLM logs EmbeddingResponse verbatim), truncated at content_max_bytes
rerank post-redaction request JSON (query + documents) the relayed rerank JSON verbatim
images post-redaction request JSON the image JSON (url or b64_json — LiteLLM does not strip response b64 either)
audio speech post-redaction request JSON (the input text) not captured (binary; LiteLLM logs no TTS response)
audio transcription/translation audio bytes never captured — the file field is represented by its sha256 (LiteLLM's file_checksum convention) alongside the verbatim text form fields (post-redaction prompt included) the post-redaction transcript

Blocked (422) responses capture nothing, matching the chat surface. All captures are post-#932-masking (builds on #703), so masked PII stays masked in exported content.

Tests

New DP E2E sls-content-capture-nontext-e2e.test.ts (real binary + etcd + mock SLS): all five scenarios assert the full logstore carries the planted request tokens AND the response content (vector value / rerank score / image URL / transcript + file sha256, never the raw audio bytes), while metadata_only receives none. Verified fail-before/pass-after (crate changes stashed → test fails).

Fixes #700

Summary by CodeRabbit

  • New Features
    • Added optional content capture in observability for embeddings, rerank, image generation, and audio endpoints (speech, transcriptions, translations).
    • When enabled, captured payloads include redacted request prompts and upstream responses (or transcript/serialized request), truncated by exporter limits.
    • Audio transcription capture now uses an uploaded audio file SHA-256 fingerprint instead of raw bytes.
  • Bug Fixes
    • Content capture is now propagated to usage/telemetry fan-out only on successful upstream handling; it’s omitted when blocked or not supported.
  • Tests
    • Added an end-to-end coverage test for content capture correctness across the above endpoint families.

…M parity)

Completes the AISIX-Cloud#947 family: the four remaining handlers fanned
observability events out with content=None, so content_mode=full
exporters received metadata only for their traffic. Scope follows
LiteLLM's standard logging payload:

- embeddings — prompt = post-redaction request JSON; response = the full
  embeddings JSON, vectors included (LiteLLM logs EmbeddingResponse
  verbatim), truncated at the capture cap
- rerank — prompt = post-redaction request JSON (query + documents);
  response = the relayed rerank JSON verbatim
- images — prompt = post-redaction request JSON; response = the image
  JSON (url or b64_json; LiteLLM does not strip response b64 either)
- audio speech — prompt = post-redaction request JSON (the input text);
  the binary audio response is not captured (LiteLLM logs no TTS
  response)
- audio transcription/translation — the audio bytes are never captured:
  the file field is represented by its sha256 (exactly LiteLLM's
  file_checksum convention) alongside the verbatim text form fields
  (post-redaction prompt included); response = the post-redaction
  transcript

Blocked (422) responses capture nothing, matching the chat surface.

Fixes #700
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e23281a1-1ee6-4626-9533-967727315265

📥 Commits

Reviewing files that changed from the base of the PR and between 95f5a3b and 3187384.

📒 Files selected for processing (2)
  • crates/aisix-proxy/src/audio.rs
  • tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts
  • crates/aisix-proxy/src/audio.rs

📝 Walkthrough

Walkthrough

This PR adds optional content capture to audio, embeddings, images, and rerank proxy handlers, and forwards captured request/response content into telemetry fan-out instead of always emitting None. It also adds an e2e test covering full and metadata-only exporter behavior.

Changes

Content Capture Implementation

Layer / File(s) Summary
Dependency addition
crates/aisix-proxy/Cargo.toml
Adds sha2 as a workspace dependency for hashing captured audio content.
Audio content capture
crates/aisix-proxy/src/audio.rs
Adds captured content to audio dispatch success, captures multipart and speech payloads, and forwards captured content through usage emission into fan-out.
Embeddings content capture
crates/aisix-proxy/src/embeddings.rs
Adds Serialize derives to request types, captures the redacted request prompt and full embedding response JSON, stores captured content on success, and forwards it into usage fan-out.
Images content capture
crates/aisix-proxy/src/images.rs
Adds captured content to image dispatch success, captures the redacted request prompt and upstream response JSON, and forwards it through usage fan-out.
Rerank content capture
crates/aisix-proxy/src/rerank.rs
Adds captured content to rerank dispatch success, captures the request prompt and upstream response bytes, and forwards it through usage fan-out.
E2E validation
tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts
Adds a test that verifies full-mode capture and metadata-only omission across embeddings, rerank, images, audio speech, and audio transcription.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AudioHandler
  participant Upstream
  participant Telemetry

  Client->>AudioHandler: transcription, translation, or speech request
  AudioHandler->>AudioHandler: redact input and compute content_capture_cap
  AudioHandler->>AudioHandler: build captured_prompt from multipart or JSON body
  AudioHandler->>Upstream: forward request
  Upstream-->>AudioHandler: transcript, translation, or speech response
  AudioHandler->>AudioHandler: build captured_content and attach to success
  AudioHandler->>Telemetry: emit_usage_event(captured_content)
Loading

Possibly related PRs

  • api7/aisix#683: Touches the same audio and rerank proxy paths, with overlapping handler-level changes in audio.rs and rerank.rs.
  • api7/aisix#694: Shares the proxy telemetry plumbing and post-redaction data flow used by this content-capture work.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning Coverage is incomplete: the new E2E only exercises happy paths and misses /v1/audio/translations plus any blocked/422 no-content case. Add at least one translation case and one negative 422/guardrail case so the capture contract is verified across the full audio scope.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Clearly states the main change: content capture for embeddings, rerank, images, and audio observability.
Linked Issues check ✅ Passed Implements content capture for the missing handlers and adds the requested e2e coverage, matching #700's coding goals.
Out of Scope Changes check ✅ Passed Changes stay within content-capture work, with the new dependency and e2e test supporting the same scope.
Security Check ✅ Passed PASS: The new content capture is opt-in, redacted before capture, and gated to full exporters; CP sink stays metadata-only. No auth/DB/TLS/ownership regressions found.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-700-content-capture

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts (1)

266-274: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Response-side markers missing from metadata_only negative check.

EMBED_RESPONSE_MARK, RERANK_RESPONSE_MARK, and IMAGES_RESPONSE_URL are asserted present in FULL_LOGSTORE (Lines 206, 220, 232) but are not included in this negative-token list, so the test doesn't verify that response content is excluded from the metadata-only export.

🧪 Proposed addition to the negative-token list
       for (const token of [
         EMBED_PROMPT_TOKEN,
+        String(EMBED_RESPONSE_MARK),
         RERANK_PROMPT_TOKEN,
         RERANK_DOC_TOKEN,
+        String(RERANK_RESPONSE_MARK),
         IMAGES_PROMPT_TOKEN,
+        IMAGES_RESPONSE_URL,
         SPEECH_PROMPT_TOKEN,
         TRANSCRIPT_PROMPT_TOKEN,
         TRANSCRIPT_RESPONSE_TOKEN,
       ]) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts` around lines 266
- 274, The metadata_only negative-token check in the nontext E2E test is missing
response-side markers, so it does not fully verify response content is excluded
from the export. Update the token loop in the `sls-content-capture-nontext-e2e`
test to include `EMBED_RESPONSE_MARK`, `RERANK_RESPONSE_MARK`, and
`IMAGES_RESPONSE_URL` alongside the existing prompt/transcript tokens. Keep the
change within the existing negative assertion block so the metadata-only case
covers both request and response markers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aisix-proxy/src/audio.rs`:
- Around line 516-518: The full-content export path in audio.rs is leaking
user-controlled upload filenames via the obj.insert call that writes
{name}_filename. Update the logic around the file_name handling so filenames are
either omitted or passed through the same PII redaction flow used elsewhere,
while still keeping the SHA-256-only representation of the file payload. Use the
existing audio export and redaction helpers in this code path to locate the
change.
- Around line 503-521: The captured_prompt builder is losing repeated multipart
fields because serde_json::Map::insert overwrites earlier entries, so preserve
all values instead of collapsing by name. Update the logic inside the
captured_prompt mapping in audio.rs to accumulate repeated fields per key in the
captured content structure (including prompt and other duplicated names), while
keeping the existing sha256/file-name handling for non-text or file fields. Use
the captured_prompt block and the fields iteration as the main place to fix
this.

In `@tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts`:
- Around line 264-276: The negative content checks on the metadata-only logstore
are racing the async export, so the assertion can pass before the event arrives.
In the sls-content-capture-nontext e2e test, update the META_LOGSTORE
verification to wait for a benign always-present marker from the same request,
using the same request flow that already waits on FULL_LOGSTORE, before checking
that EMBED_PROMPT_TOKEN, RERANK_PROMPT_TOKEN, RERANK_DOC_TOKEN,
IMAGES_PROMPT_TOKEN, SPEECH_PROMPT_TOKEN, TRANSCRIPT_PROMPT_TOKEN, and
TRANSCRIPT_RESPONSE_TOKEN are absent. This makes the assertion in the test case
around decodedTextFor and waitForToken order-independent and reliable.

---

Nitpick comments:
In `@tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts`:
- Around line 266-274: The metadata_only negative-token check in the nontext E2E
test is missing response-side markers, so it does not fully verify response
content is excluded from the export. Update the token loop in the
`sls-content-capture-nontext-e2e` test to include `EMBED_RESPONSE_MARK`,
`RERANK_RESPONSE_MARK`, and `IMAGES_RESPONSE_URL` alongside the existing
prompt/transcript tokens. Keep the change within the existing negative assertion
block so the metadata-only case covers both request and response markers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7db54474-bca2-477f-a94c-bbe980f17f21

📥 Commits

Reviewing files that changed from the base of the PR and between 81800db and 95f5a3b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • crates/aisix-proxy/Cargo.toml
  • crates/aisix-proxy/src/audio.rs
  • crates/aisix-proxy/src/embeddings.rs
  • crates/aisix-proxy/src/images.rs
  • crates/aisix-proxy/src/rerank.rs
  • tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts

Comment thread crates/aisix-proxy/src/audio.rs
Comment thread crates/aisix-proxy/src/audio.rs Outdated
Comment thread tests/e2e/src/cases/sls-content-capture-nontext-e2e.test.ts
…e-race meta assertion

CodeRabbit on #708: (1) serde_json::Map::insert overwrote repeated
multipart field values in the captured prompt while all repeats are
forwarded — append with a newline instead; (2) the upload filename is
user-controlled text that skips the redaction path — drop it from the
capture, the sha256 alone represents the file (LiteLLM parity anyway);
(3) the e2e meta-logstore negative assertions now wait for the last
request's metadata to land first, so they can't pass trivially against
a lagging store.
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.

content capture: embeddings / rerank / images / audio still fan out with content=None — decide scope

1 participant