From 571d5075976149b6477e2f7bb2da93fc2d63bd95 Mon Sep 17 00:00:00 2001 From: Di-Is Date: Sun, 15 Mar 2026 18:17:03 +0900 Subject: [PATCH 1/2] fix: pass config params to structured_output in OpenAIResponsesModel --- src/strands/models/openai_responses.py | 18 +++++-- tests/strands/models/test_openai_responses.py | 48 +++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/strands/models/openai_responses.py b/src/strands/models/openai_responses.py index 71d3f7ef7..46529727c 100644 --- a/src/strands/models/openai_responses.py +++ b/src/strands/models/openai_responses.py @@ -356,13 +356,21 @@ async def structured_output( ContextWindowOverflowException: If the input exceeds the model's context window. ModelThrottledException: If the request is throttled by OpenAI (rate limits). """ + request = self._format_request(prompt, system_prompt=system_prompt) + + excluded_keys = {"model", "input", "stream"} + parse_kwargs: dict[str, Any] = { + "model": request["model"], + "input": request["input"], + "text_format": output_model, + } + for key, value in request.items(): + if key not in excluded_keys: + parse_kwargs.setdefault(key, value) + async with openai.AsyncOpenAI(**self.client_args) as client: try: - response = await client.responses.parse( - model=self.get_config()["model_id"], - input=self._format_request(prompt, system_prompt=system_prompt)["input"], - text_format=output_model, - ) + response = await client.responses.parse(**parse_kwargs) except openai.BadRequestError as e: if hasattr(e, "code") and e.code == "context_length_exceeded": logger.warning(_CONTEXT_WINDOW_OVERFLOW_MSG) diff --git a/tests/strands/models/test_openai_responses.py b/tests/strands/models/test_openai_responses.py index 9c84f4ed4..2eead627e 100644 --- a/tests/strands/models/test_openai_responses.py +++ b/tests/strands/models/test_openai_responses.py @@ -633,6 +633,54 @@ async def test_structured_output(openai_client, model, test_output_model_cls, al assert tru_result == exp_result +@pytest.mark.asyncio +async def test_structured_output_passes_config_params(openai_client, test_output_model_cls, alist): + """Test that structured_output passes config params (max_output_tokens, reasoning, etc.) to responses.parse.""" + model = OpenAIResponsesModel( + model_id="gpt-5.4", + params={ + "max_output_tokens": 500, + "reasoning": {"effort": "high"}, + }, + ) + + messages = [{"role": "user", "content": [{"text": "Generate a person"}]}] + + mock_parsed_instance = test_output_model_cls(name="Alice", age=25) + mock_response = unittest.mock.Mock(output_parsed=mock_parsed_instance) + openai_client.responses.parse = unittest.mock.AsyncMock(return_value=mock_response) + + events = await alist(model.structured_output(test_output_model_cls, messages)) + + assert events[-1] == {"output": test_output_model_cls(name="Alice", age=25)} + + call_kwargs = openai_client.responses.parse.call_args.kwargs + assert call_kwargs["model"] == "gpt-5.4" + assert call_kwargs["text_format"] == test_output_model_cls + assert call_kwargs["max_output_tokens"] == 500 + assert call_kwargs["reasoning"] == {"effort": "high"} + assert "stream" not in call_kwargs + + +@pytest.mark.asyncio +async def test_structured_output_passes_instructions(openai_client, test_output_model_cls, alist): + """Test that structured_output passes system_prompt as instructions to responses.parse.""" + model = OpenAIResponsesModel(model_id="gpt-4o") + + messages = [{"role": "user", "content": [{"text": "Generate a person"}]}] + + mock_parsed_instance = test_output_model_cls(name="Bob", age=40) + mock_response = unittest.mock.Mock(output_parsed=mock_parsed_instance) + openai_client.responses.parse = unittest.mock.AsyncMock(return_value=mock_response) + + events = await alist(model.structured_output(test_output_model_cls, messages, system_prompt="Be helpful")) + + assert events[-1] == {"output": test_output_model_cls(name="Bob", age=40)} + + call_kwargs = openai_client.responses.parse.call_args.kwargs + assert call_kwargs["instructions"] == "Be helpful" + + @pytest.mark.asyncio async def test_stream_context_overflow_exception(openai_client, model, messages): """Test that OpenAI context overflow errors are properly converted to ContextWindowOverflowException.""" From 92acfe6743655c8ab0c36784be0c1b44c0d26bf4 Mon Sep 17 00:00:00 2001 From: Di-Is Date: Sun, 15 Mar 2026 19:03:09 +0900 Subject: [PATCH 2/2] fix: deep copy config params to prevent mutation by external SDK --- src/strands/models/openai_responses.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strands/models/openai_responses.py b/src/strands/models/openai_responses.py index 46529727c..ae95e7a83 100644 --- a/src/strands/models/openai_responses.py +++ b/src/strands/models/openai_responses.py @@ -16,6 +16,7 @@ """ import base64 +import copy import json import logging import mimetypes @@ -412,7 +413,7 @@ def _format_request( "model": self.config["model_id"], "input": input_items, "stream": True, - **cast(dict[str, Any], self.config.get("params", {})), + **copy.deepcopy(cast(dict[str, Any], self.config.get("params", {}))), } if system_prompt: