Skip to content

Python: Fix #2410: Combine text content and tool_calls into single message per OpenAI API spec#2418

Closed
ishanrajsingh wants to merge 5 commits intomicrosoft:mainfrom
ishanrajsingh:fix/openai-message-parser-split
Closed

Python: Fix #2410: Combine text content and tool_calls into single message per OpenAI API spec#2418
ishanrajsingh wants to merge 5 commits intomicrosoft:mainfrom
ishanrajsingh:fix/openai-message-parser-split

Conversation

@ishanrajsingh
Copy link
Contributor

Description

Fixes #2410

The OpenAI chat client's _openai_chat_message_parser method was incorrectly splitting messages containing both text content and function/tool calls into separate API messages. According to the OpenAI API specification, a single assistant message can and should contain both content and tool_calls fields in the same message object.

Problem

When a ChatMessage contained both text content and function calls, the parser created separate messages:
Incorrect output (before fix):
[
{"role": "assistant", "content": ["I'll help you with that calculation."]},
{"role": "assistant", "tool_calls": [{"id": "call-123", "type": "function", ...}]}
]

text

Solution

Refactored the parser to aggregate all content types into a single message:
Correct output (after fix):
[
{
"role": "assistant",
"content": ["I'll help you with that calculation."],
"tool_calls": [{"id": "call-123", "type": "function", ...}]
}
]

text

Changes Made

Core Changes

  • _chat_client.py: Refactored _openai_chat_message_parser to build a single message dictionary that accumulates both text content and tool calls
  • _responses_client.py: Applied the same fix with additional handling for Responses API-specific formats

Additional Improvements

  • Added exception handling for FunctionResultContent to properly format error messages as "Error: {exception}"
  • Fixed approval content (FunctionApprovalRequestContent and FunctionApprovalResponseContent) to be top-level items in Responses API as per specification
  • Ensured all content types are properly handled without being skipped

Testing

  • ✅ All existing tests pass (126 passed, 46 skipped)
  • ✅ Fixed two failing tests:
    • test_function_result_exception_handling
    • test_end_to_end_mcp_approval_flow
  • ✅ Verified message structure matches OpenAI API specification
  • ✅ Code formatted with ruff

Impact

API Compatibility

  • Before: May cause errors or unexpected behavior with OpenAI API due to malformed message structure
  • After: Fully compliant with OpenAI API specification

Context Accuracy

  • Before: Incorrect message structure could affect conversation history and context
  • After: Proper message structure ensures accurate context propagation

Model Behavior

  • Before: Model may receive fragmented context leading to degraded responses
  • After: Model receives complete, properly formatted context

Breaking Changes

None. This fix corrects the API format to match the specification without changing public interfaces.

Related Issues

Closes #2410

Checklist

  • Code follows the project's style guidelines
  • All tests pass
  • Exception handling added for edge cases
  • Changes are backward compatible
  • Documentation/comments updated where necessary

…nAI message

- Updated _openai_chat_message_parser in both _chat_client.py and _responses_client.py
- Now aggregates text content and tool calls into single message dict per OpenAI API spec
- Added exception handling for FunctionResultContent
- Fixed approval content handling for Responses API
- All existing tests pass
# These need special handling - add them as content
if "content" not in msg:
msg["content"] = []
msg["content"].append(self._openai_content_parser(content))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will add a dict based on our class to the list, I don't think that makes sense, could you 1) add test cases with this and 2) add integration tests that have this. This is used with MCP server side tool calling, so the output format should be determined by openai, not by our type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python [Bug] OpenAI client incorrectly splits content and tool_calls into separate messages

3 participants