diff --git a/run_agent.py b/run_agent.py index 33635ef2..c87bd351 100644 --- a/run_agent.py +++ b/run_agent.py @@ -7294,6 +7294,20 @@ class AIAgent: if reasoning_text: reasoning_text = _sanitize_surrogates(reasoning_text) + # Strip inline reasoning tags ( etc.) from the stored + # assistant content. Reasoning was already captured into + # ``reasoning_text`` above (either from structured fields or the + # inline-block fallback), so the raw tags in content are redundant. + # Leaving them in place caused reasoning to leak to messaging + # platforms (#8878, #9568), inflate context on subsequent turns + # (#9306 observed 16% content-size reduction on a real MiniMax + # session), and pollute generated session titles. One strip at the + # storage boundary cleans content for every downstream consumer: + # API replay, session transcript, gateway delivery, CLI display, + # compression, title generation. + if isinstance(_san_content, str) and _san_content: + _san_content = self._strip_think_blocks(_san_content).strip() + msg = { "role": "assistant", "content": _san_content, diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index bde5ed5a..d30445cf 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -1142,6 +1142,41 @@ class TestBuildAssistantMessage: result = agent._build_assistant_message(msg, "tool_calls") assert "extra_content" not in result["tool_calls"][0] + def test_think_blocks_stripped_from_content(self, agent): + """Inline blocks are stripped from stored content (#8878, #9568). + + The reasoning is captured into ``msg['reasoning']`` via the inline + fallback in ``_extract_reasoning``; the raw tags in ``content`` are + redundant and leak to messaging platforms / pollute titles / + inflate context if left in place. + """ + msg = _mock_assistant_msg( + content="internal reasoningThe actual answer." + ) + result = agent._build_assistant_message(msg, "stop") + assert "" not in result["content"] + assert "internal reasoning" not in result["content"] + assert "The actual answer." in result["content"] + # Reasoning preserved separately via inline extraction fallback + assert result["reasoning"] == "internal reasoning" + + def test_think_blocks_stripped_preserves_normal_content(self, agent): + """Content without reasoning tags passes through unchanged.""" + msg = _mock_assistant_msg(content="No thinking here.") + result = agent._build_assistant_message(msg, "stop") + assert result["content"] == "No thinking here." + + def test_unterminated_think_block_stripped(self, agent): + """Unterminated block (MiniMax / NIM dropped close tag) is + fully stripped from stored content.""" + msg = _mock_assistant_msg( + content="reasoning that never closes on this NIM endpoint" + ) + result = agent._build_assistant_message(msg, "stop") + assert "" not in result["content"] + assert "reasoning that never closes" not in result["content"] + assert result["content"] == "" + class TestFormatToolsForSystemMessage: def test_no_tools_returns_empty_array(self, agent):