Python: Fix array input in Chat Completions format losing user message content#5133
Python: Fix array input in Chat Completions format losing user message content#5133giles17 wants to merge 7 commits intomicrosoft:mainfrom
Conversation
…icrosoft#5112) When input was sent as an array without an explicit "type": "message" field (Chat Completions format), _is_openai_multimodal_format returned False and _convert_openai_input_to_chat_message silently skipped the item. This caused the user's query to be replaced with an empty string, making the agent return unfiltered tool results. Broaden both _is_openai_multimodal_format and the item-level check in _convert_openai_input_to_chat_message to also accept items that have a "role" field but no explicit "type" field. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix ruff SIM103 lint error by replacing if/return True/return False pattern with direct return of bool expression in _is_openai_multimodal_format. Also accept formatter-applied style changes in tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 94%
✓ Correctness
This PR only deletes a REPRODUCTION_REPORT.md file that was accidentally committed to the repository root during investigation of issue #5112. The file contained internal debugging notes, raw analysis text, and repository-local paths — none of which belong in the committed codebase. The deletion is correct and has no impact on code behavior. However, this PR does not include the actual bug fix for issue #5112 (the missing Chat Completions format handling in _executor.py). That fix should be addressed in a separate or follow-up PR.
✓ Security Reliability
This PR only deletes a REPRODUCTION_REPORT.md file that was previously committed to the repository root. The file contained investigation notes, internal file paths, and analysis details for issue #5112. Removing it is a cleanup action with no security or reliability concerns — the file contained no secrets, and its removal does not affect any code paths, tests, or runtime behavior. No code changes are present in this diff.
✓ Test Coverage
This PR only deletes REPRODUCTION_REPORT.md, a temporary investigation artifact that should not be committed to the repository. The actual bug fix (handling Chat Completions format in _executor.py) and its corresponding tests were added in prior commits on this branch. The existing tests on the branch adequately cover the fix: they test detection of Chat Completions format in _is_openai_multimodal_format, conversion with string content, and conversion with list content. The deletion of the report has no impact on test coverage.
✓ Design Approach
This PR deletes REPRODUCTION_REPORT.md, a temporary investigation artifact that should not have been committed to the repository. The file contained notes from a bug reproduction session for issue #5112 but no actual code fix. Deleting it is the correct cleanup action. There are no design concerns with this change — removing stale investigation artifacts from the repo is appropriate housekeeping.
Suggestions
- Ensure the actual bug fix for issue #5112 (broadening the
item_typecheck in_convert_openai_input_to_chat_messageat_executor.pyline 634) is included in this or a follow-up PR, since this diff only removes the investigation report.
Automated review by giles17's agents
There was a problem hiding this comment.
Pull request overview
This PR updates the DevUI /v1/responses executor’s input parsing so that message arrays sent in Chat Completions-style shape ({"role": "...", "content": ...} without a top-level "type": "message") are recognized and converted into an Agent Framework Message, preventing loss of user prompt text.
Changes:
- Extend
_is_openai_multimodal_formatto also detect Chat Completions-style message dicts (no"type", but has"role"). - Extend
_convert_openai_input_to_chat_messageto treat"role"-bearing dicts as message items, including whencontentis a plain string. - Add regression tests covering detection and conversion for Chat Completions-style inputs.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
python/packages/devui/agent_framework_devui/_executor.py |
Broadens OpenAI input detection and conversion logic to accept Chat Completions-style message objects. |
python/packages/devui/tests/devui/test_multimodal_workflow.py |
Adds tests to validate the new detection/conversion behavior for Chat Completions-style inputs. |
…ns parsing - Tighten _is_openai_multimodal_format to validate role is a known string value and content is str|list, reducing false positives - Skip non-user messages (system/assistant/tool/developer) during conversion to prevent them being treated as user text - Rename test to clarify it covers Chat Completions envelope with Responses API content parts - Add end-to-end regression test through _parse_workflow_input with JSON-stringified Chat Completions array - Add tests for non-user message skipping and malformed input rejection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 86%
✓ Correctness
The fix correctly addresses the reported bug where Chat Completions format messages (without an explicit
"type": "message"field) had their user content silently dropped. The two changes—(1) skipping non-user messages in_convert_openai_input_to_chat_messageand (2) tightening_is_openai_multimodal_formatto validate role, content key, and content type—are logically sound and well-tested. Theisinstance(content, str | list)union syntax is safe given the project'srequires-python >= 3.10constraint. No correctness bugs, race conditions, or regressions are introduced.
✓ Security Reliability
The fix correctly addresses the root cause: Chat Completions-format input items without an explicit "type": "message" field were silently skipped, causing user content to be lost. The PR (1) broadens _is_openai_multimodal_format to accept Chat Completions-style dicts with proper validation (role allowlist, content type check), and (2) skips non-user messages in the converter to prevent system/assistant content from being injected as user content. Both changes are sound from a security perspective. One minor reliability gap: when every item in the array is non-user (e.g., only system messages), all items are skipped and the fallback at line 784 silently produces an empty text message sent as role="user", which could cause confusing model behavior with no diagnostic signal beyond a debug log. Tests cover the key scenarios well.
✗ Test Coverage
New tests cover the main fix paths well: Chat Completions format detection, non-user message skipping, JSON-stringified regression path, and malformed input rejection. However, there is a notable gap—there is no test for the edge case where ALL messages in the input array are non-user roles (e.g., only system messages). The new skip logic on lines 635-638 would skip every item, resulting in an empty contents list that triggers the fallback at line 783 producing a Message with empty text. This is an important behavioral consequence of the change that should be explicitly covered and asserted. Additionally, the Responses API format path (type: message with non-user role) is not tested for the skip behavior, only the Chat Completions path is.
✓ Design Approach
The fix correctly addresses the root cause: Chat Completions format items lacking an explicit
"type": "message"field were silently skipped, causing the user's query to be lost. Both changes — stricter detection in_is_openai_multimodal_formatand role-filtering in_convert_input_to_chat_message— are sound. One design concern worth flagging: the non-user role skipping (role != "user") is applied unconditionally to both Responses API (type == "message") items and Chat Completions items. This is a behavior change for Responses API arrays containing assistant-turn messages, but it is actually correct since the output is alwaysMessage(role="user", ...). A subtler issue is that_is_openai_multimodal_formatnow accepts arrays whose first element has rolesystem,assistant, ortool(all are invalid_roles), which means a system-only or assistant-only message list is routed into_convert_input_to_chat_message, all items get skipped, and the fallbackContent.from_text(text="")silently produces an empty user message rather than signalling an error or falling through to another handler. This is a pre-existing silent-failure pattern but the new routing makes it reachable for more inputs.
Flagged Issues
- No test covers the edge case where all input messages are non-user roles (e.g., [{"role": "system", "content": ".."}]). The new skip logic causes all items to be skipped, falling through to line 783-784 which creates a Message with empty text content. This is a significant behavioral change that needs an explicit test to document and verify the expected outcome.
Suggestions
- Consider whether
_is_openai_multimodal_formatshould look beyond the first item for a user-role message. Currently, an input like[{"role": "tool", "content": "result"}]is classified as multimodal format and processed into an empty-text fallback message, which may be surprising. - When all input items are skipped (non-user), the fallback at line 783-784 silently creates an empty user message. Consider logging a warning here (e.g., "All input items were non-user; no user content extracted") so operators can diagnose unexpected empty responses in production.
- The _is_openai_multimodal_format check only validates the first item in the list. A malformed second item (e.g., with content set to an integer) would pass format detection but be silently ignored during conversion. This is pre-existing behavior and low-risk, but worth noting.
- Add a test for the Responses API format ({"type": "message", "role": "system", ...}) to verify non-user messages are also skipped when using that envelope, not just the Chat Completions format.
- Consider adding positive cases to test_is_openai_multimodal_format_rejects_malformed_input to verify all valid roles (system, assistant, tool, developer) are accepted, since the new valid_roles set is part of the contract.
- The
valid_rolesset used in_is_openai_multimodal_formatincludessystem,assistant, andtool. Combined with the new non-user skipping logic, a list whose first (or only) item is a non-user role will be detected as multimodal format and routed into_convert_input_to_chat_message, but then every item will be skipped, silently producing an emptyMessage. Consider either (a) restricting_is_openai_multimodal_formatto returnTrueonly when at least the first element hasrole == "user", or (b) emitting a warning (not just a debug log) when_convert_input_to_chat_messageproduces a zero-content result after skipping non-user items, so the silent data loss is visible in production logs.
Automated review by giles17's agents
…case tests - Add logger.warning when all input items are non-user and the empty content fallback is triggered, making silent data loss visible in production logs. - Add test for all-non-user messages (Chat Completions format) verifying the empty-text fallback behavior. - Add test for non-user messages in Responses API format (type: message envelope) verifying they are skipped correctly. - Add test verifying all valid roles (user, system, assistant, tool, developer) are accepted by _is_openai_multimodal_format. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback: _is_openai_multimodal_format now requires at least one item with role='user' before classifying input as multimodal format. This prevents non-user-only arrays (e.g., system-only) from being routed into the conversion path where all items would be skipped, silently producing an empty message. Changes: - Restructure _is_openai_multimodal_format to check both Responses API and Chat Completions paths, then verify user-role presence - Update test_is_openai_multimodal_format_accepts_all_valid_roles to test non-user roles alongside a user companion item - Add test_is_openai_multimodal_format_rejects_non_user_only for both Chat Completions and Responses API format arrays Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ves differently with input when sent as string vs array - Difference is consistent
giles17
left a comment
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 92%
✓ Correctness
This diff contains only cosmetic changes: (1) an unnecessary
cast()added to_is_openai_multimodal_formatwhereisinstance(item, dict)already narows the type at runtime, and (2) reformatting of test assertions from single-line to parenthesized multi-line style. The actual bug fix (line 634, broadening theitem_typecheck in_convert_openai_input_to_chat_message) was applied in a prior commit and is not part of this diff. There are no correctness issues.
✓ Security Reliability
This PR contains only cosmetic changes: (1) adding a
cast()call in_is_openai_multimodal_formatthat is a no-op at runtime (Python'scastdoes nothing), and (2) reformatting test assertions for line length. There are no functional, security, or reliability changes. The underlying bug fix for parsing Chat Completions format input (theitem_type is None and "role" in item_dictcondition at line 634) appears to have already been applied in a prior commit and is not part of this diff. No new security or reliability issues are introduced.
✓ Test Coverage
This PR contains only cosmetic changes: (1) an unnecessary
cast()wrapping in_is_openai_multimodal_formaton_executor.pyline 862 — theisinstance(item, dict)guard already narows the type, making the cast redundant; (2) reformatting of multi-line assert statements in the test file for style. No new tests are added and no behavioral changes are made. The core bug fix (line 634, handling Chat Completions format withouttype) was already landed in a prior PR and is present in this branch. Since there are no behavior changes, there are no test coverage gaps introduced by this diff.
✓ Design Approach
The diff makes two narrow changes: (1) adds a
cast(dict[str, Any], item)call in the_is_openai_multimodal_formatgenerator expression for type-checker satisfaction, and (2) reformats multi-line test assertions for readability. Neither change alters runtime behaviour. The cast is consistent with the existing pattern used at line 632 of the same file. The test changes are purely cosmetic. Notably, the actual behavioural bug fix described in the issue — broadening theitem_typecheck at line 634 to accept Chat Completions format items that lack an explicit"type"field — is already present in the local file but is not part of this diff, meaning it was committed separately. There are no design, abstraction, or correctness issues introduced by what is in this diff.
Suggestions
- The
cast(dict[str, Any], item)on line 862 of_executor.pyis redundant —isinstance(item, dict)already short-circuits theandexpression, so.get("role")is only called on dicts. If added purely for type-checker satisfaction (e.g., pyright strict mode), consider adding a brief comment explaining why; otherwise, it's unnecessary noise anditem.get("role")can be used directly as before.
Automated review by giles17's agents
Motivation and Context
When users send input to the
/responsesendpoint as an array of message objects using Chat Completions format ({"role": "user", "content": "..."}without a"type"field), the executor fails to recognize it as structured input. This causes the agent to ignore the user's actual query and return unfiltered tool output instead of properly analyzing and filtering results per the user's request.Fixes #5112
Description
The root cause is that
_is_openai_multimodal_formatand_convert_input_to_chat_messageonly recognized message objects containing an explicit"type": "message"field (Responses API format), but Chat Completions format messages use{"role": "..", "content": "..."}without atypefield. The fix extends both methods to also detect and handle the Chat Completions format by checking for the presence of a"role"key when"type"is absent. This ensures that array-based input is correctly parsed into aMessageobject with the user's text preserved, so the agent processes the query identically regardless of whether input is sent as a plain string or a message array. Regression tests verify detection and conversion of Chat Completions format messages with both string and list content.Contribution Checklist