From 0fd5c7e9edda4b28c4b2603a4ab6708221882958 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 27 Aug 2025 10:19:01 -0700 Subject: [PATCH 1/7] Reraise workflow failure errors from OpenAI's UserError --- temporalio/contrib/openai_agents/__init__.py | 2 + .../contrib/openai_agents/_openai_runner.py | 43 ++++++++++++---- .../openai_agents/_temporal_openai_agents.py | 8 ++- temporalio/worker/_workflow_instance.py | 10 ++-- temporalio/workflow.py | 7 +++ tests/contrib/openai_agents/test_openai.py | 51 +++++++++++++++++++ 6 files changed, 105 insertions(+), 16 deletions(-) diff --git a/temporalio/contrib/openai_agents/__init__.py b/temporalio/contrib/openai_agents/__init__.py index 274f5b98b..1bd3d9afb 100644 --- a/temporalio/contrib/openai_agents/__init__.py +++ b/temporalio/contrib/openai_agents/__init__.py @@ -9,6 +9,7 @@ """ from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters +from temporalio.contrib.openai_agents._openai_runner import AgentsWorkflowFailure from temporalio.contrib.openai_agents._temporal_openai_agents import ( OpenAIAgentsPlugin, TestModel, @@ -21,6 +22,7 @@ from . import workflow __all__ = [ + "AgentsWorkflowFailure", "OpenAIAgentsPlugin", "ModelActivityParameters", "workflow", diff --git a/temporalio/contrib/openai_agents/_openai_runner.py b/temporalio/contrib/openai_agents/_openai_runner.py index 396d74546..1ff7f8614 100644 --- a/temporalio/contrib/openai_agents/_openai_runner.py +++ b/temporalio/contrib/openai_agents/_openai_runner.py @@ -5,6 +5,7 @@ from agents import ( Agent, + AgentsException, Handoff, RunConfig, RunContextWrapper, @@ -21,6 +22,16 @@ from temporalio import workflow from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters from temporalio.contrib.openai_agents._temporal_model_stub import _TemporalModelStub +from temporalio.exceptions import ApplicationError, TemporalError + + +class AgentsWorkflowFailure(TemporalError): + """Error that occurs when the agents SDK raises an error which should terminate the calling workflow. + + .. warning:: + This exception is experimental and may change in future versions. + Use with caution in production environments. + """ class TemporalOpenAIRunner(AgentRunner): @@ -136,16 +147,28 @@ async def on_invoke( handoffs=new_handoffs, ) - return await self._runner.run( - starting_agent=convert_agent(starting_agent, None), - input=input, - context=context, - max_turns=max_turns, - hooks=hooks, - run_config=run_config, - previous_response_id=previous_response_id, - session=session, - ) + try: + return await self._runner.run( + starting_agent=convert_agent(starting_agent, None), + input=input, + context=context, + max_turns=max_turns, + hooks=hooks, + run_config=run_config, + previous_response_id=previous_response_id, + session=session, + ) + except AgentsException as e: + # In order for workflow failures to properly fail the workflow, we need to rewrap them in + # a Temporal error + if e.__cause__ and workflow.is_workflow_failure_exception(e.__cause__): + reraise = AgentsWorkflowFailure( + f"Workflow failure exception in Agents Framework: {e}" + ) + reraise.__traceback__ = e.__traceback__ + raise reraise from e.__cause__ + else: + raise e def run_sync( self, diff --git a/temporalio/contrib/openai_agents/_temporal_openai_agents.py b/temporalio/contrib/openai_agents/_temporal_openai_agents.py index 73b9723d0..92d086bd2 100644 --- a/temporalio/contrib/openai_agents/_temporal_openai_agents.py +++ b/temporalio/contrib/openai_agents/_temporal_openai_agents.py @@ -27,7 +27,10 @@ from temporalio.client import ClientConfig, Plugin from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters -from temporalio.contrib.openai_agents._openai_runner import TemporalOpenAIRunner +from temporalio.contrib.openai_agents._openai_runner import ( + AgentsWorkflowFailure, + TemporalOpenAIRunner, +) from temporalio.contrib.openai_agents._temporal_trace_provider import ( TemporalTraceProvider, ) @@ -284,6 +287,9 @@ def configure_worker(self, config: WorkerConfig) -> WorkerConfig: config["activities"] = list(config.get("activities") or []) + [ ModelActivity(self._model_provider).invoke_model_activity ] + config["workflow_failure_exception_types"] = list( + config.get("workflow_failure_exception_types") or [] + ) + [AgentsWorkflowFailure] return self.next_worker_plugin.configure_worker(config) async def run_worker(self, worker: Worker) -> None: diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index c93155672..b58c13867 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -414,7 +414,7 @@ def activate( # We want some errors during activation, like those that can happen # during payload conversion, to be able to fail the workflow not the # task - if self._is_workflow_failure_exception(err): + if self.is_workflow_failure_exception(err): try: self._set_workflow_failure(err) except Exception as inner_err: @@ -629,7 +629,7 @@ async def run_update() -> None: # Validation failures are always update failures. We reuse # workflow failure logic to decide task failure vs update # failure after validation. - if not past_validation or self._is_workflow_failure_exception(err): + if not past_validation or self.is_workflow_failure_exception(err): if command is None: command = self._add_command() command.update_response.protocol_instance_id = ( @@ -1939,7 +1939,7 @@ def _convert_payloads( # Don't wrap payload conversion errors that would fail the workflow raise except Exception as err: - if self._is_workflow_failure_exception(err): + if self.is_workflow_failure_exception(err): raise raise RuntimeError("Failed decoding arguments") from err @@ -1982,7 +1982,7 @@ def _instantiate_workflow_object(self) -> Any: return workflow_instance - def _is_workflow_failure_exception(self, err: BaseException) -> bool: + def is_workflow_failure_exception(self, err: BaseException) -> bool: # An exception is a failure instead of a task fail if it's already a # failure error or if it is a timeout error or if it is an instance of # any of the failure types in the worker or workflow-level setting @@ -2192,7 +2192,7 @@ async def _run_top_level_workflow_function(self, coro: Awaitable[None]) -> None: err ): self._add_command().cancel_workflow_execution.SetInParent() - elif self._is_workflow_failure_exception(err): + elif self.is_workflow_failure_exception(err): # All other failure errors fail the workflow self._set_workflow_failure(err) else: diff --git a/temporalio/workflow.py b/temporalio/workflow.py index 423d5289b..de23edcc9 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -897,6 +897,9 @@ def workflow_get_current_details(self) -> str: ... @abstractmethod def workflow_set_current_details(self, details: str): ... + @abstractmethod + def is_workflow_failure_exception(self, err: BaseException) -> bool: ... + _current_update_info: contextvars.ContextVar[UpdateInfo] = contextvars.ContextVar( "__temporal_current_update_info" @@ -981,6 +984,10 @@ def memo() -> Mapping[str, Any]: return _Runtime.current().workflow_memo() +def is_workflow_failure_exception(err: BaseException) -> bool: + return _Runtime.current().is_workflow_failure_exception(err) + + @overload def memo_value(key: str, default: Any = temporalio.common._arg_unset) -> Any: ... diff --git a/tests/contrib/openai_agents/test_openai.py b/tests/contrib/openai_agents/test_openai.py index 7c3df0897..78fc94e39 100644 --- a/tests/contrib/openai_agents/test_openai.py +++ b/tests/contrib/openai_agents/test_openai.py @@ -318,6 +318,10 @@ async def run(self, question: str) -> str: ActivityWeatherService.get_weather_method, start_to_close_timeout=timedelta(seconds=10), ), + openai_agents.workflow.activity_as_tool( + get_weather_failure, + start_to_close_timeout=timedelta(seconds=10), + ), ], ) result = await Runner.run( @@ -462,6 +466,53 @@ async def test_tool_workflow(client: Client, use_local_model: bool): ) +@activity.defn +async def get_weather_failure(city: str) -> Weather: + """ + Get the weather for a given city. + """ + raise ApplicationError("No weather", non_retryable=True) + + +class TestWeatherFailureModel(StaticTestModel): + responses = [ + ResponseBuilders.tool_call('{"city":"Tokyo"}', "get_weather_failure"), + ] + + +async def test_tool_failure_workflow(client: Client): + new_config = client.config() + new_config["plugins"] = [ + openai_agents.OpenAIAgentsPlugin( + model_params=ModelActivityParameters( + start_to_close_timeout=timedelta(seconds=30) + ), + model_provider=TestModelProvider(TestWeatherFailureModel()), + ) + ] + client = Client(**new_config) + + async with new_worker( + client, + ToolsWorkflow, + activities=[ + get_weather_failure, + ], + ) as worker: + workflow_handle = await client.start_workflow( + ToolsWorkflow.run, + "What is the weather in Tokio?", + id=f"tools-failure-workflow-{uuid.uuid4()}", + task_queue=worker.task_queue, + execution_timeout=timedelta(seconds=2), + ) + with pytest.raises(WorkflowFailureError) as e: + result = await workflow_handle.result() + cause = e.value.cause + assert isinstance(cause, ApplicationError) + assert "Workflow failure exception in Agents Framework" in cause.message + + @pytest.mark.parametrize("use_local_model", [True, False]) async def test_nexus_tool_workflow( client: Client, env: WorkflowEnvironment, use_local_model: bool From c0faa1e3ea67bcbd278408e803803d67c54df351 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 27 Aug 2025 10:57:19 -0700 Subject: [PATCH 2/7] Docstring --- temporalio/workflow.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/temporalio/workflow.py b/temporalio/workflow.py index de23edcc9..99a637b5e 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -985,6 +985,11 @@ def memo() -> Mapping[str, Any]: def is_workflow_failure_exception(err: BaseException) -> bool: + """Checks if the given exception is a workflow failure in the current workflow. + + Returns: + True if the given exception is a workflow failure in the current workflow. + """ return _Runtime.current().is_workflow_failure_exception(err) From ca86ef65eea26035b60ffbaa6e8dab34e9d93981 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 28 Aug 2025 08:05:31 -0700 Subject: [PATCH 3/7] PR feedback --- temporalio/contrib/openai_agents/__init__.py | 4 +- .../contrib/openai_agents/_openai_runner.py | 15 ++----- .../openai_agents/_temporal_openai_agents.py | 4 +- temporalio/contrib/openai_agents/workflow.py | 9 ++++ temporalio/worker/_workflow_instance.py | 42 +++++++++---------- temporalio/workflow.py | 6 +-- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/temporalio/contrib/openai_agents/__init__.py b/temporalio/contrib/openai_agents/__init__.py index 1bd3d9afb..f4d97f5d4 100644 --- a/temporalio/contrib/openai_agents/__init__.py +++ b/temporalio/contrib/openai_agents/__init__.py @@ -9,7 +9,7 @@ """ from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters -from temporalio.contrib.openai_agents._openai_runner import AgentsWorkflowFailure +from temporalio.contrib.openai_agents._openai_runner import AgentsWorkflowError from temporalio.contrib.openai_agents._temporal_openai_agents import ( OpenAIAgentsPlugin, TestModel, @@ -22,7 +22,7 @@ from . import workflow __all__ = [ - "AgentsWorkflowFailure", + "AgentsWorkflowError", "OpenAIAgentsPlugin", "ModelActivityParameters", "workflow", diff --git a/temporalio/contrib/openai_agents/_openai_runner.py b/temporalio/contrib/openai_agents/_openai_runner.py index 1ff7f8614..4f9dbbf65 100644 --- a/temporalio/contrib/openai_agents/_openai_runner.py +++ b/temporalio/contrib/openai_agents/_openai_runner.py @@ -22,16 +22,7 @@ from temporalio import workflow from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters from temporalio.contrib.openai_agents._temporal_model_stub import _TemporalModelStub -from temporalio.exceptions import ApplicationError, TemporalError - - -class AgentsWorkflowFailure(TemporalError): - """Error that occurs when the agents SDK raises an error which should terminate the calling workflow. - - .. warning:: - This exception is experimental and may change in future versions. - Use with caution in production environments. - """ +from temporalio.contrib.openai_agents.workflow import AgentsWorkflowError class TemporalOpenAIRunner(AgentRunner): @@ -161,8 +152,8 @@ async def on_invoke( except AgentsException as e: # In order for workflow failures to properly fail the workflow, we need to rewrap them in # a Temporal error - if e.__cause__ and workflow.is_workflow_failure_exception(e.__cause__): - reraise = AgentsWorkflowFailure( + if e.__cause__ and workflow.is_failure_exception(e.__cause__): + reraise = AgentsWorkflowError( f"Workflow failure exception in Agents Framework: {e}" ) reraise.__traceback__ = e.__traceback__ diff --git a/temporalio/contrib/openai_agents/_temporal_openai_agents.py b/temporalio/contrib/openai_agents/_temporal_openai_agents.py index 92d086bd2..583626a75 100644 --- a/temporalio/contrib/openai_agents/_temporal_openai_agents.py +++ b/temporalio/contrib/openai_agents/_temporal_openai_agents.py @@ -28,7 +28,7 @@ from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters from temporalio.contrib.openai_agents._openai_runner import ( - AgentsWorkflowFailure, + AgentsWorkflowError, TemporalOpenAIRunner, ) from temporalio.contrib.openai_agents._temporal_trace_provider import ( @@ -289,7 +289,7 @@ def configure_worker(self, config: WorkerConfig) -> WorkerConfig: ] config["workflow_failure_exception_types"] = list( config.get("workflow_failure_exception_types") or [] - ) + [AgentsWorkflowFailure] + ) + [AgentsWorkflowError] return self.next_worker_plugin.configure_worker(config) async def run_worker(self, worker: Worker) -> None: diff --git a/temporalio/contrib/openai_agents/workflow.py b/temporalio/contrib/openai_agents/workflow.py index d9f27e679..861de9cef 100644 --- a/temporalio/contrib/openai_agents/workflow.py +++ b/temporalio/contrib/openai_agents/workflow.py @@ -263,3 +263,12 @@ class ToolSerializationError(TemporalError): To fix this error, ensure your tool returns string-convertible values or modify the tool to return a string representation of the result. """ + + +class AgentsWorkflowError(TemporalError): + """Error that occurs when the agents SDK raises an error which should terminate the calling workflow. + + .. warning:: + This exception is experimental and may change in future versions. + Use with caution in production environments. + """ diff --git a/temporalio/worker/_workflow_instance.py b/temporalio/worker/_workflow_instance.py index b58c13867..f0984cc84 100644 --- a/temporalio/worker/_workflow_instance.py +++ b/temporalio/worker/_workflow_instance.py @@ -414,7 +414,7 @@ def activate( # We want some errors during activation, like those that can happen # during payload conversion, to be able to fail the workflow not the # task - if self.is_workflow_failure_exception(err): + if self.workflow_is_failure_exception(err): try: self._set_workflow_failure(err) except Exception as inner_err: @@ -629,7 +629,7 @@ async def run_update() -> None: # Validation failures are always update failures. We reuse # workflow failure logic to decide task failure vs update # failure after validation. - if not past_validation or self.is_workflow_failure_exception(err): + if not past_validation or self.workflow_is_failure_exception(err): if command is None: command = self._add_command() command.update_response.protocol_instance_id = ( @@ -1686,6 +1686,23 @@ def workflow_set_current_details(self, details: str): self._assert_not_read_only("set current details") self._current_details = details + def workflow_is_failure_exception(self, err: BaseException) -> bool: + # An exception is a failure instead of a task fail if it's already a + # failure error or if it is a timeout error or if it is an instance of + # any of the failure types in the worker or workflow-level setting + wf_failure_exception_types = self._defn.failure_exception_types + if self._dynamic_failure_exception_types is not None: + wf_failure_exception_types = self._dynamic_failure_exception_types + return ( + isinstance(err, temporalio.exceptions.FailureError) + or isinstance(err, asyncio.TimeoutError) + or any(isinstance(err, typ) for typ in wf_failure_exception_types) + or any( + isinstance(err, typ) + for typ in self._worker_level_failure_exception_types + ) + ) + #### Calls from outbound impl #### # These are in alphabetical order and all start with "_outbound_". @@ -1939,7 +1956,7 @@ def _convert_payloads( # Don't wrap payload conversion errors that would fail the workflow raise except Exception as err: - if self.is_workflow_failure_exception(err): + if self.workflow_is_failure_exception(err): raise raise RuntimeError("Failed decoding arguments") from err @@ -1982,23 +1999,6 @@ def _instantiate_workflow_object(self) -> Any: return workflow_instance - def is_workflow_failure_exception(self, err: BaseException) -> bool: - # An exception is a failure instead of a task fail if it's already a - # failure error or if it is a timeout error or if it is an instance of - # any of the failure types in the worker or workflow-level setting - wf_failure_exception_types = self._defn.failure_exception_types - if self._dynamic_failure_exception_types is not None: - wf_failure_exception_types = self._dynamic_failure_exception_types - return ( - isinstance(err, temporalio.exceptions.FailureError) - or isinstance(err, asyncio.TimeoutError) - or any(isinstance(err, typ) for typ in wf_failure_exception_types) - or any( - isinstance(err, typ) - for typ in self._worker_level_failure_exception_types - ) - ) - def _warn_if_unfinished_handlers(self) -> None: def warnable(handler_executions: Iterable[HandlerExecution]): return [ @@ -2192,7 +2192,7 @@ async def _run_top_level_workflow_function(self, coro: Awaitable[None]) -> None: err ): self._add_command().cancel_workflow_execution.SetInParent() - elif self.is_workflow_failure_exception(err): + elif self.workflow_is_failure_exception(err): # All other failure errors fail the workflow self._set_workflow_failure(err) else: diff --git a/temporalio/workflow.py b/temporalio/workflow.py index 99a637b5e..50118a2bb 100644 --- a/temporalio/workflow.py +++ b/temporalio/workflow.py @@ -898,7 +898,7 @@ def workflow_get_current_details(self) -> str: ... def workflow_set_current_details(self, details: str): ... @abstractmethod - def is_workflow_failure_exception(self, err: BaseException) -> bool: ... + def workflow_is_failure_exception(self, err: BaseException) -> bool: ... _current_update_info: contextvars.ContextVar[UpdateInfo] = contextvars.ContextVar( @@ -984,13 +984,13 @@ def memo() -> Mapping[str, Any]: return _Runtime.current().workflow_memo() -def is_workflow_failure_exception(err: BaseException) -> bool: +def is_failure_exception(err: BaseException) -> bool: """Checks if the given exception is a workflow failure in the current workflow. Returns: True if the given exception is a workflow failure in the current workflow. """ - return _Runtime.current().is_workflow_failure_exception(err) + return _Runtime.current().workflow_is_failure_exception(err) @overload From 998b59853b64a2e3950eea5c6eadfbc801cffb92 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 28 Aug 2025 11:19:08 -0700 Subject: [PATCH 4/7] PR feedback --- temporalio/contrib/openai_agents/__init__.py | 2 +- temporalio/contrib/openai_agents/_temporal_openai_agents.py | 4 ++-- temporalio/contrib/openai_agents/workflow.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/temporalio/contrib/openai_agents/__init__.py b/temporalio/contrib/openai_agents/__init__.py index f4d97f5d4..998cf61eb 100644 --- a/temporalio/contrib/openai_agents/__init__.py +++ b/temporalio/contrib/openai_agents/__init__.py @@ -9,7 +9,6 @@ """ from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters -from temporalio.contrib.openai_agents._openai_runner import AgentsWorkflowError from temporalio.contrib.openai_agents._temporal_openai_agents import ( OpenAIAgentsPlugin, TestModel, @@ -18,6 +17,7 @@ from temporalio.contrib.openai_agents._trace_interceptor import ( OpenAIAgentsTracingInterceptor, ) +from temporalio.contrib.openai_agents.workflow import AgentsWorkflowError from . import workflow diff --git a/temporalio/contrib/openai_agents/_temporal_openai_agents.py b/temporalio/contrib/openai_agents/_temporal_openai_agents.py index 583626a75..4885d8f35 100644 --- a/temporalio/contrib/openai_agents/_temporal_openai_agents.py +++ b/temporalio/contrib/openai_agents/_temporal_openai_agents.py @@ -24,11 +24,11 @@ import temporalio.client import temporalio.worker -from temporalio.client import ClientConfig, Plugin +from temporalio.client import ClientConfig +from temporalio.contrib.openai_agents import AgentsWorkflowError from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters from temporalio.contrib.openai_agents._openai_runner import ( - AgentsWorkflowError, TemporalOpenAIRunner, ) from temporalio.contrib.openai_agents._temporal_trace_provider import ( diff --git a/temporalio/contrib/openai_agents/workflow.py b/temporalio/contrib/openai_agents/workflow.py index 861de9cef..2f69866ce 100644 --- a/temporalio/contrib/openai_agents/workflow.py +++ b/temporalio/contrib/openai_agents/workflow.py @@ -266,7 +266,7 @@ class ToolSerializationError(TemporalError): class AgentsWorkflowError(TemporalError): - """Error that occurs when the agents SDK raises an error which should terminate the calling workflow. + """Error that occurs when the agents SDK raises an error which should terminate the calling workflow or update. .. warning:: This exception is experimental and may change in future versions. From fdf7072e3bd6aa9125f904b5576ebd67fcbe907e Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Thu, 28 Aug 2025 16:10:03 -0700 Subject: [PATCH 5/7] Fix circular dependency --- temporalio/contrib/openai_agents/_temporal_openai_agents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/temporalio/contrib/openai_agents/_temporal_openai_agents.py b/temporalio/contrib/openai_agents/_temporal_openai_agents.py index 4885d8f35..0c698aa98 100644 --- a/temporalio/contrib/openai_agents/_temporal_openai_agents.py +++ b/temporalio/contrib/openai_agents/_temporal_openai_agents.py @@ -25,7 +25,6 @@ import temporalio.client import temporalio.worker from temporalio.client import ClientConfig -from temporalio.contrib.openai_agents import AgentsWorkflowError from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters from temporalio.contrib.openai_agents._openai_runner import ( @@ -37,6 +36,7 @@ from temporalio.contrib.openai_agents._trace_interceptor import ( OpenAIAgentsTracingInterceptor, ) +from temporalio.contrib.openai_agents.workflow import AgentsWorkflowError from temporalio.contrib.pydantic import ( PydanticPayloadConverter, ToJsonOptions, From 9f3db534ef9ce7b423e4eea62f425db8407b9f40 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 3 Sep 2025 08:29:59 -0700 Subject: [PATCH 6/7] Remove a few live openai tests, the models don't always do what they need to --- tests/contrib/openai_agents/test_openai.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/contrib/openai_agents/test_openai.py b/tests/contrib/openai_agents/test_openai.py index 78fc94e39..d9c3816b0 100644 --- a/tests/contrib/openai_agents/test_openai.py +++ b/tests/contrib/openai_agents/test_openai.py @@ -1960,11 +1960,7 @@ async def run(self, question: str) -> str: return result.final_output -@pytest.mark.parametrize("use_local_model", [True, False]) -async def test_code_interpreter_tool(client: Client, use_local_model): - if not use_local_model and not os.environ.get("OPENAI_API_KEY"): - pytest.skip("No openai API key") - +async def test_code_interpreter_tool(client: Client): new_config = client.config() new_config["plugins"] = [ openai_agents.OpenAIAgentsPlugin( @@ -1972,8 +1968,6 @@ async def test_code_interpreter_tool(client: Client, use_local_model): start_to_close_timeout=timedelta(seconds=60) ), model_provider=TestModelProvider(CodeInterpreterModel()) - if use_local_model - else None, ) ] client = Client(**new_config) @@ -1990,8 +1984,7 @@ async def test_code_interpreter_tool(client: Client, use_local_model): execution_timeout=timedelta(seconds=60), ) result = await workflow_handle.result() - if use_local_model: - assert result == "Over 9000" + assert result == "Over 9000" class HostedMCPModel(StaticTestModel): @@ -2062,11 +2055,7 @@ def approve(_: MCPToolApprovalRequest) -> MCPToolApprovalFunctionResult: return result.final_output -@pytest.mark.parametrize("use_local_model", [True, False]) -async def test_hosted_mcp_tool(client: Client, use_local_model): - if not use_local_model and not os.environ.get("OPENAI_API_KEY"): - pytest.skip("No openai API key") - +async def test_hosted_mcp_tool(client: Client): new_config = client.config() new_config["plugins"] = [ openai_agents.OpenAIAgentsPlugin( @@ -2074,8 +2063,6 @@ async def test_hosted_mcp_tool(client: Client, use_local_model): start_to_close_timeout=timedelta(seconds=120) ), model_provider=TestModelProvider(HostedMCPModel()) - if use_local_model - else None, ) ] client = Client(**new_config) @@ -2092,8 +2079,7 @@ async def test_hosted_mcp_tool(client: Client, use_local_model): execution_timeout=timedelta(seconds=120), ) result = await workflow_handle.result() - if use_local_model: - assert result == "Some language" + assert result == "Some language" class AssertDifferentModelProvider(ModelProvider): From 04180cb5e7c1663e78768d2397c6ab7ad6b6e112 Mon Sep 17 00:00:00 2001 From: Tim Conley Date: Wed, 3 Sep 2025 08:30:18 -0700 Subject: [PATCH 7/7] Lint --- tests/contrib/openai_agents/test_openai.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/contrib/openai_agents/test_openai.py b/tests/contrib/openai_agents/test_openai.py index d9c3816b0..11613296b 100644 --- a/tests/contrib/openai_agents/test_openai.py +++ b/tests/contrib/openai_agents/test_openai.py @@ -1967,7 +1967,7 @@ async def test_code_interpreter_tool(client: Client): model_params=ModelActivityParameters( start_to_close_timeout=timedelta(seconds=60) ), - model_provider=TestModelProvider(CodeInterpreterModel()) + model_provider=TestModelProvider(CodeInterpreterModel()), ) ] client = Client(**new_config) @@ -2062,7 +2062,7 @@ async def test_hosted_mcp_tool(client: Client): model_params=ModelActivityParameters( start_to_close_timeout=timedelta(seconds=120) ), - model_provider=TestModelProvider(HostedMCPModel()) + model_provider=TestModelProvider(HostedMCPModel()), ) ] client = Client(**new_config)