diff --git a/application/llm/openai.py b/application/llm/openai.py index ddbe5e3d..e3b9000e 100644 --- a/application/llm/openai.py +++ b/application/llm/openai.py @@ -461,15 +461,12 @@ class OpenAILLM(BaseLLM): @staticmethod def _responses_content_parts(role, content): """Translate a cleaned chat ``content`` value into Responses content - parts (``input_text``/``input_image``/``input_file``). - - Assistant text from prior turns is replayed as ``input_text`` too: a - Responses easy-input message only accepts the input_* content types - regardless of role (``output_text`` is reserved for full - output-message items, which carry id/status/annotations). + parts. The Responses API enforces the content-part type by message + role: assistant turns require ``output_text`` (``input_text`` is + rejected with a 400), while user/system turns require ``input_text``. + Images/files use ``input_image``/``input_file``. """ - _ = role - text_type = "input_text" + text_type = "output_text" if role == "assistant" else "input_text" parts = [] if content is None: return parts diff --git a/tests/llm/test_openai_responses.py b/tests/llm/test_openai_responses.py index 67a021d8..127edf49 100644 --- a/tests/llm/test_openai_responses.py +++ b/tests/llm/test_openai_responses.py @@ -102,7 +102,9 @@ def test_to_responses_input_tool_roundtrip(monkeypatch): "arguments": '{"q":"x"}', }, {"type": "function_call_output", "call_id": "call_1", "output": "result"}, - {"role": "assistant", "content": [{"type": "input_text", "text": "final"}]}, + # The Responses API requires output_text (not input_text) for the + # assistant role; input_text 400s. Locked in here so it can't regress. + {"role": "assistant", "content": [{"type": "output_text", "text": "final"}]}, ]