Skip to content

Commit 021344b

Browse files
giulio-leonegiulio-leoneCopilot
authored
fix(openai_responses): use output_text for assistant messages in multi-turn conversations (#1851)
Signed-off-by: Giulio Leone <6887247+giulio-leone@users.noreply.github.com> Co-authored-by: giulio-leone <giulio.leone@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3406ef4 commit 021344b

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

src/strands/models/openai_responses.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
import openai # noqa: E402 - must import after version check
5252

53-
from ..types.content import ContentBlock, Messages # noqa: E402
53+
from ..types.content import ContentBlock, Messages, Role # noqa: E402
5454
from ..types.exceptions import ContextWindowOverflowException, ModelThrottledException # noqa: E402
5555
from ..types.streaming import StreamEvent # noqa: E402
5656
from ..types.tools import ToolChoice, ToolResult, ToolSpec, ToolUse # noqa: E402
@@ -467,7 +467,7 @@ def _format_request_messages(cls, messages: Messages) -> list[dict[str, Any]]:
467467
contents = message["content"]
468468

469469
formatted_contents = [
470-
cls._format_request_message_content(content)
470+
cls._format_request_message_content(content, role=role)
471471
for content in contents
472472
if not any(block_type in content for block_type in ["toolResult", "toolUse"])
473473
]
@@ -502,11 +502,15 @@ def _format_request_messages(cls, messages: Messages) -> list[dict[str, Any]]:
502502
]
503503

504504
@classmethod
505-
def _format_request_message_content(cls, content: ContentBlock) -> dict[str, Any]:
505+
def _format_request_message_content(
506+
cls, content: ContentBlock, *, role: Role = "user"
507+
) -> dict[str, Any]:
506508
"""Format an OpenAI compatible content block.
507509
508510
Args:
509511
content: Message content.
512+
role: Message role ("user" or "assistant"). Controls text content
513+
type: "input_text" for user, "output_text" for assistant.
510514
511515
Returns:
512516
OpenAI compatible content block.
@@ -526,7 +530,8 @@ def _format_request_message_content(cls, content: ContentBlock) -> dict[str, Any
526530
return {"type": "input_image", "image_url": data_url}
527531

528532
if "text" in content:
529-
return {"type": "input_text", "text": content["text"]}
533+
text_type = "output_text" if role == "assistant" else "input_text"
534+
return {"type": text_type, "text": content["text"]}
530535

531536
raise TypeError(f"content_type=<{next(iter(content))}> | unsupported type")
532537

tests/strands/models/test_openai_responses.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def test_format_request_messages(system_prompt):
248248
},
249249
{
250250
"role": "assistant",
251-
"content": [{"type": "input_text", "text": "call tool"}],
251+
"content": [{"type": "output_text", "text": "call tool"}],
252252
},
253253
{
254254
"type": "function_call",
@@ -265,6 +265,58 @@ def test_format_request_messages(system_prompt):
265265
assert tru_result == exp_result
266266

267267

268+
def test_format_request_messages_assistant_text_uses_output_text():
269+
"""Assistant text content must use output_text, not input_text.
270+
271+
Regression test for multi-turn conversations failing because the OpenAI
272+
Responses API rejects input_text in assistant messages.
273+
See: https://github.com/strands-agents/sdk-python/issues/1850
274+
"""
275+
messages = [
276+
{
277+
"content": [{"text": "Say hello"}],
278+
"role": "user",
279+
},
280+
{
281+
"content": [{"text": "Hello!"}],
282+
"role": "assistant",
283+
},
284+
{
285+
"content": [{"text": "Say goodbye"}],
286+
"role": "user",
287+
},
288+
]
289+
290+
result = OpenAIResponsesModel._format_request_messages(messages)
291+
292+
assert result[0] == {
293+
"role": "user",
294+
"content": [{"type": "input_text", "text": "Say hello"}],
295+
}
296+
assert result[1] == {
297+
"role": "assistant",
298+
"content": [{"type": "output_text", "text": "Hello!"}],
299+
}
300+
assert result[2] == {
301+
"role": "user",
302+
"content": [{"type": "input_text", "text": "Say goodbye"}],
303+
}
304+
305+
306+
def test_format_request_message_content_role_assistant():
307+
"""_format_request_message_content uses output_text for assistant role."""
308+
content = {"text": "response text"}
309+
result = OpenAIResponsesModel._format_request_message_content(content, role="assistant")
310+
assert result == {"type": "output_text", "text": "response text"}
311+
312+
313+
def test_format_request_message_content_role_user():
314+
"""_format_request_message_content uses input_text for user role (default)."""
315+
content = {"text": "question"}
316+
result = OpenAIResponsesModel._format_request_message_content(content, role="user")
317+
assert result == {"type": "input_text", "text": "question"}
318+
319+
268320
def test_format_request(model, messages, tool_specs, system_prompt):
269321
tru_request = model._format_request(messages, tool_specs, system_prompt)
270322
exp_request = {

0 commit comments

Comments
 (0)