Skip to content

Commit e267a64

Browse files
authored
fix: handle reasoning content in OpenAIResponsesModel request formatting (#2013)
1 parent de9b149 commit e267a64

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

src/strands/models/openai_responses.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,13 @@ async def stream(
240240
if model_state is not None and response_id:
241241
model_state["response_id"] = response_id
242242

243-
elif event.type == "response.reasoning_text.delta":
244-
# Reasoning content streaming (for o1/o3 reasoning models)
243+
elif event.type in (
244+
"response.reasoning_text.delta",
245+
"response.reasoning_summary_text.delta",
246+
):
247+
# Reasoning content streaming:
248+
# - reasoning_text: full chain-of-thought (gpt-oss models)
249+
# - reasoning_summary_text: condensed summary (o-series models)
245250
chunks, data_type = self._stream_switch_content("reasoning_content", data_type)
246251
for chunk in chunks:
247252
yield chunk
@@ -510,10 +515,15 @@ def _format_request_messages(cls, messages: Messages) -> list[dict[str, Any]]:
510515
role = message["role"]
511516
contents = message["content"]
512517

518+
if any("reasoningContent" in content for content in contents):
519+
logger.warning(
520+
"reasoningContent is not yet supported in multi-turn conversations with the Responses API"
521+
)
522+
513523
formatted_contents = [
514524
cls._format_request_message_content(content, role=role)
515525
for content in contents
516-
if not any(block_type in content for block_type in ["toolResult", "toolUse"])
526+
if not any(block_type in content for block_type in ["toolResult", "toolUse", "reasoningContent"])
517527
]
518528

519529
formatted_tool_calls = [

tests/strands/models/test_openai_responses.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -596,9 +596,16 @@ async def test_stream_response_incomplete(openai_client, model, agenerator, alis
596596

597597

598598
@pytest.mark.asyncio
599-
async def test_stream_reasoning_content(openai_client, model, agenerator, alist):
600-
"""Test that reasoning content (o1/o3 models) is streamed correctly."""
601-
mock_reasoning_event = unittest.mock.Mock(type="response.reasoning_text.delta", delta="Let me think...")
599+
@pytest.mark.parametrize(
600+
"event_type",
601+
[
602+
"response.reasoning_text.delta",
603+
"response.reasoning_summary_text.delta",
604+
],
605+
)
606+
async def test_stream_reasoning_content(openai_client, model, agenerator, alist, event_type):
607+
"""Test that reasoning content is streamed correctly for both full and summary reasoning events."""
608+
mock_reasoning_event = unittest.mock.Mock(type=event_type, delta="Let me think...")
602609
mock_text_event = unittest.mock.Mock(type="response.output_text.delta", delta="The answer is 42")
603610
mock_complete_event = unittest.mock.Mock(
604611
type="response.completed",
@@ -1152,3 +1159,34 @@ async def test_stream_stateful(openai_client, model_id, agenerator, alist):
11521159
"usage": {"inputTokens": 10, "outputTokens": 5, "totalTokens": 15},
11531160
"metrics": {"latencyMs": 0},
11541161
}
1162+
1163+
1164+
def test_format_request_messages_excludes_reasoning_content(caplog):
1165+
"""Test that reasoningContent blocks are filtered from messages with a warning."""
1166+
messages = [
1167+
{
1168+
"content": [{"text": "Hello"}],
1169+
"role": "user",
1170+
},
1171+
{
1172+
"content": [
1173+
{"reasoningContent": {"reasoningText": {"text": "Let me think..."}}},
1174+
{"text": "The answer is 42"},
1175+
],
1176+
"role": "assistant",
1177+
},
1178+
{
1179+
"content": [{"text": "Thanks"}],
1180+
"role": "user",
1181+
},
1182+
]
1183+
1184+
with caplog.at_level("WARNING"):
1185+
result = OpenAIResponsesModel._format_request_messages(messages)
1186+
1187+
assert result == [
1188+
{"role": "user", "content": [{"type": "input_text", "text": "Hello"}]},
1189+
{"role": "assistant", "content": [{"type": "output_text", "text": "The answer is 42"}]},
1190+
{"role": "user", "content": [{"type": "input_text", "text": "Thanks"}]},
1191+
]
1192+
assert "reasoningContent is not yet supported" in caplog.text

tests_integ/models/test_model_mantle.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,28 @@ def test_responses_server_side_conversation(stateful_model):
7272

7373
result = agent("What is my name?")
7474
assert "alice" in str(result).lower()
75+
76+
77+
def test_reasoning_content_multi_turn(client_args):
78+
"""Test that reasoning content from gpt-oss models doesn't break multi-turn conversations."""
79+
model = OpenAIResponsesModel(
80+
model_id="openai.gpt-oss-120b",
81+
client_args=client_args,
82+
params={"reasoning": {"effort": "low"}},
83+
)
84+
agent = Agent(model=model, system_prompt="Reply in one short sentence.", callback_handler=None)
85+
86+
result1 = agent("What is 2+2?")
87+
assert "4" in str(result1)
88+
89+
# Verify reasoning content was produced
90+
has_reasoning = any(
91+
"reasoningContent" in block
92+
for msg in agent.messages
93+
if msg["role"] == "assistant"
94+
for block in msg["content"]
95+
)
96+
assert has_reasoning
97+
98+
# Second turn should not raise despite reasoningContent in message history
99+
agent("What about 3+3?")

0 commit comments

Comments
 (0)