yetuge 34a105c084
fix(middleware): preserve message fields when PII redaction rebuilds a user message (#5968)
* fix(middleware): preserve message fields when PII redaction rebuilds a user message

PiiRedactionMiddleware._process_request rebuilt the redacted HumanMessage
with a hand-constructed constructor call copying only content, id, name and
additional_kwargs, silently dropping response_metadata and every other field
of the original. That is the third and last site of the family already fixed
for ThreadDataMiddleware (#5823) and InputSanitizationMiddleware (#5875);
the review on #5823 scoped the follow-up to the two input-sanitization sites
and left this one out.

The middleware was also inconsistent with itself: _redact_tool_message in the
same file already rebuilds with model_copy and carries the comment explaining
that a hand-built constructor call silently drops response_metadata.

The user message now rebuilds with msg.model_copy(update={...}). The content
is deep-copied because _redact_content appends untouched content blocks by
reference: without the copy, mutating a non-text block on the redacted
message would write through to the message retained in thread state.

Red/green: test_redacted_user_message_preserves_other_fields asserts
response_metadata/id/name/additional_kwargs survive the rewrite — red on main
(response_metadata == {}), green here. A second test guards the aliasing
behaviour above and goes red if the deep copy is dropped. Both suites 258
passed; ruff 0.15.12 check + format clean repo-wide.

Fixes #5967

Signed-off-by: yetuge <2219677952@qq.com>

* fix(middleware): keep the PII rewrite on copy failure and detach metadata

Addresses the two copy-boundary findings on the previous commit.

1. A content block may carry a value whose copy raises. deepcopy(content) then
   raised out of _process_request, _try_process fell back to the original
   request, and the handler received raw PII unredacted. Copying now falls back
   to a shallow per-container copy, so a copy failure still produces a redacted
   message and never bypasses redaction.

2. msg.model_copy(update=...) is shallow, so the rebuilt message and the
   original shared one response_metadata dict: mutating metadata on the
   model-facing message wrote through to the request message retained in thread
   state. response_metadata is now copied independently.

Red/green: test_preserved_metadata_is_not_shared_with_the_original and
test_redaction_survives_a_content_block_that_cannot_be_copied both fail on the
reviewed commit (the second with the raw address reaching the handler) and pass
here. Suites: pii 77 passed, and pii + input_sanitization + uploads 327 passed.
ruff check + format --check clean repo-wide (1694 files).

Also drops the "third and last site" wording from the previous commit message's
claim: UploadsMiddleware is a further site of the same pattern (#5972).

Signed-off-by: yetuge <2219677952@qq.com>

---------

Signed-off-by: yetuge <2219677952@qq.com>
2026-09-28 14:21:21 +08:00
..