Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion python/packages/openai/agent_framework_openai/_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1484,10 +1484,22 @@ def _prepare_message_for_openai(
# (replays_local_storage) still need stripping when the request also carries a continuation
# marker, since the server-stored items would otherwise duplicate the inline ones. Without
# storage, standalone reasoning items are invalid per the API ("reasoning was provided
# without its required following item"), so the reasoning branch always drops.
# without its required following item"), so only keep reasoning inline when it is paired
# with a hosted MCP call in the same assistant message.
has_hosted_mcp_call = any(
item.type == "mcp_server_tool_call" and getattr(item, "call_id", None) for item in message.contents
)
for content in message.contents:
match content.type:
case "text_reasoning":
if not request_uses_service_side_storage and has_hosted_mcp_call:
prepared_reasoning = self._prepare_content_for_openai(
message.role,
content,
replays_local_storage=replays_local_storage,
)
if prepared_reasoning:
all_messages.append(prepared_reasoning)
continue
Comment on lines +1487 to 1503
case "function_result":
if request_uses_service_side_storage:
Expand Down
96 changes: 96 additions & 0 deletions python/packages/openai/tests/openai/test_openai_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5518,6 +5518,63 @@ def test_prepare_messages_for_openai_serializes_mcp_server_tool_call_as_mcp_call
assert "output" not in item or item["output"] is None


def test_prepare_messages_for_openai_keeps_reasoning_with_hosted_mcp_call_storage_off() -> None:
client = OpenAIChatClient(model="test-model", api_key="test-key")

messages = [
Message(
role="assistant",
contents=[
Content.from_text_reasoning(
id="rs_abc123",
text="Checking the hosted MCP tool",
additional_properties={"status": "completed"},
),
Content.from_mcp_server_tool_call(
call_id="mcp_def456",
tool_name="search",
server_name="api_specs",
arguments='{"q": "cats"}',
),
],
),
]

result = client._prepare_messages_for_openai(messages, request_uses_service_side_storage=False)

types = [item.get("type") for item in result if isinstance(item, dict)]
assert types == ["reasoning", "mcp_call"]
assert result[0]["id"] == "rs_abc123"
assert result[1]["id"] == "mcp_def456"


def test_prepare_messages_for_openai_drops_reasoning_and_hosted_mcp_call_with_storage() -> None:
client = OpenAIChatClient(model="test-model", api_key="test-key")

messages = [
Message(
role="assistant",
contents=[
Content.from_text_reasoning(
id="rs_abc123",
text="Checking the hosted MCP tool",
additional_properties={"status": "completed"},
),
Content.from_mcp_server_tool_call(
call_id="mcp_def456",
tool_name="search",
server_name="api_specs",
arguments='{"q": "cats"}',
),
],
),
]

result = client._prepare_messages_for_openai(messages, request_uses_service_side_storage=True)

assert result == []


def test_prepare_messages_for_openai_coalesces_mcp_call_and_result_into_single_item() -> None:
"""An mcp_server_tool_call followed by an mcp_server_tool_result with the
same call_id (in same or separate Messages) must produce ONE mcp_call
Expand Down Expand Up @@ -5563,6 +5620,45 @@ def test_prepare_messages_for_openai_coalesces_mcp_call_and_result_into_single_i
assert fco_items == [], f"unexpected orphan function_call_output items: {fco_items}"


def test_prepare_messages_for_openai_keeps_reasoning_with_coalesced_hosted_mcp_result() -> None:
client = OpenAIChatClient(model="test-model", api_key="test-key")

messages = [
Message(
role="assistant",
contents=[
Content.from_text_reasoning(
id="rs_abc123",
text="Need the MCP result",
additional_properties={"status": "completed"},
),
Content.from_mcp_server_tool_call(
call_id="mcp_def456",
tool_name="search",
server_name="api_specs",
arguments='{"q": "cats"}',
),
],
),
Message(
role="tool",
contents=[
Content.from_mcp_server_tool_result(
call_id="mcp_def456",
output=[Content.from_text(text="found 10 cats")],
)
],
),
]

result = client._prepare_messages_for_openai(messages, request_uses_service_side_storage=False)

assert [item.get("type") for item in result if isinstance(item, dict)] == ["reasoning", "mcp_call"]
assert result[0]["id"] == "rs_abc123"
assert result[1]["id"] == "mcp_def456"
assert result[1]["output"] == "found 10 cats"


def test_prepare_messages_for_openai_drops_orphan_mcp_server_tool_result() -> None:
"""When an mcp_server_tool_result has no matching mcp_server_tool_call in
the message list, it must be dropped, NOT serialized as a
Expand Down