Yufeng He e325c90b24
fix(models): preserve and budget structured MindIE tool results (#6208)
* fix(models): preserve structured MindIE tool results

Signed-off-by: lovett55 <xbzz101800@163.com>

* fix(models): redact and bound structured MindIE tool results

* fix(models): keep structured MindIE ToolMessage content visible

_fix_messages flattened list content keeping only string and type=text
blocks, so a JSON tool result became an empty <tool_response> and the
model could not see the tool output on the next turn. Serialize type=json
blocks onto the same text channel; the existing XML escaping applies to
the serialized text unchanged.

Fixes #6010

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(models): harden the MindIE json channel from review

Review follow-ups on the structured-content fix:

- json.dumps can also raise ValueError (circular reference); catch it
  alongside TypeError so the payload degrades to str() instead of
  failing the whole request normalization.
- A {"type": "json"} block with no json key keeps the old drop
  behavior instead of emitting a literal "null" into the frame.
- PiiRedactionMiddleware now redacts json block payloads leaf-wise
  (nested dict/list strings scanned, keys and non-string leaves kept),
  so the newly serialized channel participates in the configured
  redaction policy for user messages and remote tool results alike.
  Without it, redaction-enabled deployments would ship structured PII
  raw once the provider serializes the block.

Tests: fallback degrade for both exception paths, missing-key drop,
leaf-wise redaction on the model-call and tool boundaries, and a
middleware-to-MindIE regression pinning redaction through provider
serialization. Red/green verified: the 6 new redaction/json tests
fail without the source changes, 155 pass with them.

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(models): close MindIE structured tool redaction gaps

* fix(agents): budget structured json tool output before provider normalization

Willem's P2 on #6208: _message_text() returned None for {"type": "json"}
blocks, so a large structured ToolMessage slipped past both the tool-call
and history budget gates and reached MindIE's serializer at full size,
ignoring the externalize threshold and the fallback cap.

Render those blocks with the same json.dumps(..., ensure_ascii=False)
serialization the MindIE adapter applies (str() fallback included), so
they count toward the budget everywhere _message_text is consulted.
Images and other non-textual blocks still skip enforcement.

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(mindie): render json blocks for tool messages only

InputSanitizationMiddleware neutralizes strings and text blocks, so a
{"type": "json"} block smuggled into a genuine user message reaches
_fix_messages with its payload untouched. Rendering it into the text
channel handed the model an unescaped, unframed injection (e.g. a
literal <system-reminder>), bypassing the guardrail entirely.

Gate the json-block serialization on ToolMessage: structured tool
results keep riding the text channel (and still get html.escape plus
the <tool_response> framing), while user/assistant json blocks go back
to the old drop behavior. Adds unit coverage for the drop paths plus a
chain regression that runs a genuine user message through the
sanitization middleware and then _fix_messages, asserting the smuggled
payload never reaches the model-bound text.

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* fix(models): preserve configured budgets in combined MindIE fix

---------

Signed-off-by: lovett55 <xbzz101800@163.com>
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Co-authored-by: lovett55 <xbzz101800@163.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-06 12:16:18 +08:00
..