diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 5da7e0ccd4..bed32e436b 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -1223,11 +1223,22 @@ async def _responses_create( if model_settings.get('openai_include_web_search_sources'): include.append('web_search_call.action.sources') + # When there are no input messages and we're not reusing a previous response, + # the OpenAI API will reject a request without any input. To avoid this provide + # an explicit empty user message. + if not openai_messages and not previous_response_id: + openai_messages = [ + responses.EasyInputMessageParam( + role='user', + content='', + ) + ] + try: extra_headers = model_settings.get('extra_headers', {}) extra_headers.setdefault('User-Agent', get_user_agent()) return await self.client.responses.create( - input=openai_messages, + input=cast(responses.ResponseInputParam, openai_messages), model=self._model_name, instructions=instructions, parallel_tool_calls=model_settings.get('parallel_tool_calls', OMIT), diff --git a/tests/models/test_openai_responses.py b/tests/models/test_openai_responses.py index 7433841cde..14db779714 100644 --- a/tests/models/test_openai_responses.py +++ b/tests/models/test_openai_responses.py @@ -7442,3 +7442,52 @@ def get_meaning_of_life() -> int: }, ] ) + + +async def test_openai_responses_runs_with_deps_only_and_sends_input( + allow_model_requests: None, +): + from pydantic import BaseModel + + c = response_message( + [ + ResponseOutputMessage( + id='output-1', + content=[ResponseOutputText(text='ok', type='output_text', annotations=[])], + role='assistant', + status='completed', + type='message', + ) + ] + ) + + mock_client = MockOpenAIResponses.create_mock(c) + model = OpenAIResponsesModel('gpt-4o', provider=OpenAIProvider(openai_client=mock_client)) + + class Payload(BaseModel): + topic: str + sentences: int + + payload = Payload(topic='artificial intelligence', sentences=3) + + agent = Agent(model=model, instructions='Generate an article.', deps_type=Payload) + + @agent.instructions + def instr(ctx: Any) -> str: + # no explicit input passed to run(); this uses ctx.deps + return f'Topic: {ctx.deps.topic}\nSentences: {ctx.deps.sentences}' + + # Run with only deps + result = await agent.run(deps=payload) + assert result.output == 'ok' + + response_kwargs = get_mock_responses_kwargs(mock_client) + assert response_kwargs, 'Responses API was not called' + + kw = response_kwargs[0] + assert 'input' in kw or 'text' in kw, "Expected 'input' or 'text' in responses.create kwargs" + + if 'input' in kw: + assert kw['input'], "Responses 'input' should not be empty when deps are provided" + else: + assert kw['text'], "Responses 'text' config should be set when no input messages were built"