Skip to content

feat(obs): record the request body on failed chat/messages/responses requests - #749

Merged
jarvis9443 merged 2 commits into
mainfrom
feat/failure-content-capture
Jul 10, 2026
Merged

feat(obs): record the request body on failed chat/messages/responses requests#749
jarvis9443 merged 2 commits into
mainfrom
feat/failure-content-capture

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

Problem

Content capture was structurally success-only: DispatchFailure carried no content field and every failure-path emit passed None. A 4xx/5xx row in SLS showed status, error class and attempt metadata — but never what was sent, so triaging "is this a caller problem or a gateway/upstream problem" required reproducing the call (AISIX-Cloud#1013).

Change

Failed requests on the three routing endpoints (/v1/chat/completions, /v1/messages, /v1/responses) now capture the (post-mask) request body into the prompt content field, under exactly the same rules as the success path — only when a content_mode: full exporter is enabled, capped at content_max_bytes, body-only (headers/credentials are never part of captured content):

  • pre-dispatch terminal events (guardrail input block, validation, budget, model-not-found) carry the prompt;
  • all-targets-failed: there is no terminal event, so the prompt rides the last failed attempt — the one whose status the caller saw; earlier attempts stay content-less so a large prompt is never duplicated ×N;
  • billed-then-blocked (output guardrail, chat): the terminal event carries the prompt; the blocked model output intentionally stays out of the log — blocking it and then archiving it would defeat the block;
  • 401/403 stay body-less — a body adds nothing to an authorization failure, and callers probing keys shouldn't get their payloads archived (per the issue's own caution for auth failures).

Masking note: PII mask guardrails rewrite the request in place before dispatch, so captured failure bodies are post-mask whenever the failure occurred at or after the guardrail stage; failures before that stage (e.g. malformed-parameter 400s) log the body as received — same semantics as the success path, where capture is also post-mask.

Deliberately out of scope

completions/embeddings/rerank/images/audio emit no usage events at all on failure today (success-only emitters) — there is no record to attach content to. That's a separate, pre-existing gap: filed as #748. count_tokens emits no usage events by design (free endpoint).

Tests

New e2e sls-failure-content-e2e.test.ts (real aisix + etcd + mock SLS, decodes the delivered protobuf):

  1. upstream failure (chat) → record carries the prompt, no response text
  2. guardrail input block 422 → record carries the prompt, guardrail_blocked=true
  3. 403 model-forbidden → record exists without a prompt (and the sentinel never reaches the logstore)
  4. /v1/messages upstream failure → prompt present
  5. /v1/responses upstream failure → prompt present
  6. a metadata_only exporter running alongside never receives any failed-request prompt

Verified fail-before/pass-after: on unpatched main, tests 1/2/4/5 fail with "no SLS log matching … with prompt"; with this change all six pass. Neighboring content-capture and guardrail e2es pass unchanged.

Baseline: mainstream LLM proxies log the request messages on failures as well (failure logging payloads carry the input alongside structured error info), with redaction — not omission — as the sensitive-data control. This change brings failure logs to parity while keeping capture opt-in and capped.

Fixes api7/AISIX-Cloud#1013

🤖 Generated with Claude Code

…requests

Content capture was structurally success-only: DispatchFailure carried
no content and every failure-path emit passed None, so a 4xx/5xx SLS
row showed status + error class but never WHAT was sent
(AISIX-Cloud#1013).

Failed requests on the three routing endpoints (/v1/chat/completions,
/v1/messages, /v1/responses) now capture the (post-mask) request body
under the same opt-in gate and cap as the success path:

- pre-dispatch terminal events (guardrail block, validation, budget,
  model-not-found) carry the prompt;
- when every target failed there is no terminal event, so the prompt
  rides the LAST failed attempt (the status the caller saw); other
  attempts stay content-less to avoid duplicating large prompts;
- the billed-then-blocked (output guardrail) terminal event carries
  the prompt; the blocked model output intentionally stays out;
- 401/403 stay body-less — the body adds nothing to an authorization
  failure and callers probing keys shouldn't get payloads archived.

The remaining handlers (completions/embeddings/rerank/images/audio)
emit no usage events at all on failure — with or without content —
which is a separate gap, filed as a follow-up.

Fixes api7/AISIX-Cloud#1013
@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: 3 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: 2c8e7fd3-9769-488c-af51-3998d3c02d4e

📥 Commits

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

📒 Files selected for processing (4)
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • tests/e2e/src/cases/sls-failure-content-e2e.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/failure-content-capture

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

…e exports post-mask text

Audit findings on the initial revision:
- HIGH: the mask-action rewrite ran only after the input block check
  passed, so the new failure-path capture exported the PRE-mask body
  for blocked (422) requests — the one case the success path would
  have masked. The three endpoints now run the mask rewrite before
  returning the block error. (Remote Bedrock segment masks stay
  skipped on the dead request — those entities are never locally
  detected; noted in the block-arm comment.)
- /v1/responses billed-then-blocked (output guardrail) now carries the
  prompt like chat/messages; the blocked output itself stays out.
- e2e additions: post-mask capture on a blocked request (raw email
  must never reach the logstore), all-targets-failed places the prompt
  on exactly the LAST attempt record, fallback-then-success keeps the
  failed attempt content-less, and the metadata_only assertion now has
  a vacuous-pass guard.
@jarvis9443
jarvis9443 merged commit 31357d1 into main Jul 10, 2026
12 checks passed
@jarvis9443
jarvis9443 deleted the feat/failure-content-capture 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