Python: fix: encrypted_content dropped in reasoning parsing when summaries present#4690
Python: fix: encrypted_content dropped in reasoning parsing when summaries present#4690jgarrison929 wants to merge 2 commits intomicrosoft:mainfrom
Conversation
…re present When _parse_response_from_openai encounters a reasoning item with both summary and encrypted_content, the encrypted_content was silently dropped because only the fallback branch (no content, no summary) captured it. This fix extracts encrypted_content once at the top of the reasoning case and propagates it through all three branches (content, summary-only, fallback) so it round-trips correctly via _prepare_content_for_openai. The streaming parser was already correct (not affected). Fixes microsoft#4644
moonbox3
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 90%
✓ Correctness
The diff correctly fixes a bug (#4644) where
encrypted_contentwas silently dropped when a reasoning item contained both summaries/content and encrypted payload. The fix extractsencrypted_contentonce at the top of the reasoning case block and propagates it through all three branches (content, summary-only, fallback). The two new regression tests properly cover the content+summary+encrypted and summary-only+encrypted scenarios. The logic, data flow, and test assertions are all correct.
✗ Security Reliability
The fix correctly propagates
encrypted_contentthrough all branches of the non-streaming_parse_response_from_openaimethod, preventing silent data loss. However, the parallel streaming code path (_process_streaming_events, lines ~2036-2070) contains the exact same bug pattern —encrypted_contentis only captured in thenot added_reasoningfallback branch and is dropped whencontentis present — and is left unfixed. This is a reliability concern because encrypted reasoning payloads will still be silently lost during streaming responses. No security issues were found with the change itself.
✗ Test Coverage
The two new tests properly cover the main regression from #4644 (content+summary+encrypted and summary-only+encrypted paths). However, there are gaps: (1) the pre-existing
test_response_content_creation_with_reasoningtest uses a bare MagicMock formock_reasoning_itemwithout settingencrypted_content = None, meaninggetattr(item, 'encrypted_content', None)now returns a truthy MagicMock (since MagicMock auto-creates attributes), silently injecting a bogus encrypted_content into additional_properties — this test should be updated; (2) there is no dedicated test for the refactored fallback branch (no content, no summary, only encrypted_content) which was the only branch that previously worked correctly; (3) there is no negative test asserting encrypted_content is absent from additional_properties when the field is None/missing.
✗ Design Approach
The fix correctly addresses the reported bug:
encrypted_contentwas silently dropped on the non-streaming parse path whencontentorsummaryitems were also present. The implementation — extractingencrypted_contentonce and propagating it into every branch — is sound for the common single-step case. Two issues are worth noting: (1) The identical streaming parse path (around line 2060) has the exact same bug but was not patched, making this an overly narrow fix. (2) When a reasoning item has N content steps,encrypted_contentis stamped onto each of the N Content objects; on round-trip,_prepare_content_for_openaiemits each Content as a separate{"type": "reasoning", "encrypted_content": ...}API object, duplicating the payload N times. The multi-step duplication is a pre-existing structural problem (splitting one reasoning item into N Content objects), but attachingencrypted_contentto every sub-item worsens it.
Flagged Issues
- The streaming path in
_process_streaming_events(~lines 2036-2070 of_responses_client.py) has the identicalencrypted_contentdata-loss bug:encrypted_contentis only captured in thenot added_reasoningfallback branch and is dropped wheneverevent_item.contentis present. The fix is incomplete—streaming callers will still silently lose encrypted payloads. The same extraction-and-propagation pattern from the non-streaming fix should be applied here. - The existing
test_response_content_creation_with_reasoning(line 513) createsmock_reasoning_item = MagicMock()without settingencrypted_content = None. Because MagicMock auto-creates attributes,getattr(item, 'encrypted_content', None)now returns a truthy MagicMock, silently injecting a fakeencrypted_contentinto every reasoning Content'sadditional_properties. The mock should explicitly setmock_reasoning_item.encrypted_content = None.
Suggestions
- Apply the same
encrypted_content = getattr(event_item, 'encrypted_content', None)extraction at the top of thecase 'reasoning'block in the streaming path (~line 2037), and propagate it intoadditional_propertiesin both the content-iteration loop and the summary/fallback branches, mirroring the non-streaming fix. - Add a test for the fallback branch (no content, no summary, only
encrypted_content) to ensure the refactoring from inlinegetattrto the pre-extracted variable didn't break the original working path. - In
test_response_reasoning_preserves_encrypted_content_with_summary, also assert that the summary-branch Content (reasoning_contents[1]) carriesencrypted_content, not just the first content-branch element. - Add a negative test where
encrypted_contentis None, confirmingadditional_propertiesis either None or does not contain theencrypted_contentkey, to guard against accidental truthy leaks. - When
item.contenthas multiple entries, attachingencrypted_contentto every Content object causes the round-trip through_prepare_content_for_openaito emit duplicate{"type": "reasoning", "encrypted_content": ...}API objects. Consider attaching it only to the first item (index == 0) to avoid payload duplication.
Automated review by moonbox3's agents
- Apply same encrypted_content extraction to streaming _process_streaming_events - Fix MagicMock leak in existing reasoning tests (set encrypted_content=None) - Assert encrypted_content on summary branch Content, not just content branch - Add negative test for None encrypted_content case
|
Thanks for the thorough review @moonbox3. All items addressed in ff01ba3:
136/136 tests pass (1 pre-existing integration skip requiring API key). |
Motivation and Context
Fixes #4644
When
_parse_response_from_openaiencounters a reasoning item with bothsummaryandencrypted_content, theencrypted_contentis silently dropped. This is because the threeifbranches in the"reasoning"case use anadded_reasoningflag that makes only the fallback (no-content, no-summary) branch captureencrypted_content.Description
This PR:
encrypted_contentonce at the top of the"reasoning"case blockadditional_propertiesin all three branches (content, summary-only, fallback) — not just the fallbacktest_response_reasoning_preserves_encrypted_content_with_summary— verifies encrypted_content survives when both content and summary are presenttest_response_reasoning_preserves_encrypted_content_summary_only— verifies encrypted_content survives when only summary (no clear-text content) is presentThe streaming parser is not affected (event ordering means encrypted_content is captured correctly via the
response.output_item.donehandler).The serialization side (
_prepare_content_for_openai) already expectsencrypted_contentinadditional_properties, so this fix restores correct round-trip behavior.Contribution Checklist