Skip to content

Commit 0f3c577

Browse files
authored
OpenAI/plugin (#956)
* Initial rough framework for plugins * Ensure plugin modification happen before any other initialization * Openai Agents plugin PoC * Remove extra import/exports * Format * Use local tuner for type inference * Add docstrings and fix merge issues * Remove tests duplicated by merge * Fix tests * Rename plugin + a few fixes * Lint
1 parent 7c57a76 commit 0f3c577

File tree

5 files changed

+673
-562
lines changed

5 files changed

+673
-562
lines changed

temporalio/contrib/openai_agents/__init__.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,19 @@
88
Use with caution in production environments.
99
"""
1010

11-
from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity
1211
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
13-
from temporalio.contrib.openai_agents._trace_interceptor import (
14-
OpenAIAgentsTracingInterceptor,
15-
)
1612
from temporalio.contrib.openai_agents.temporal_openai_agents import (
13+
OpenAIAgentsPlugin,
1714
TestModel,
1815
TestModelProvider,
19-
set_open_ai_agent_temporal_overrides,
2016
)
2117

2218
from . import workflow
2319

2420
__all__ = [
25-
"ModelActivity",
21+
"OpenAIAgentsPlugin",
2622
"ModelActivityParameters",
2723
"workflow",
28-
"set_open_ai_agent_temporal_overrides",
29-
"OpenAIAgentsTracingInterceptor",
3024
"TestModel",
3125
"TestModelProvider",
3226
]

temporalio/contrib/openai_agents/temporal_openai_agents.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,20 @@
2121
from agents.tracing.provider import DefaultTraceProvider
2222
from openai.types.responses import ResponsePromptParam
2323

24+
import temporalio.client
25+
import temporalio.worker
26+
from temporalio.client import ClientConfig
27+
from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity
2428
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
2529
from temporalio.contrib.openai_agents._openai_runner import TemporalOpenAIRunner
2630
from temporalio.contrib.openai_agents._temporal_trace_provider import (
2731
TemporalTraceProvider,
2832
)
33+
from temporalio.contrib.openai_agents._trace_interceptor import (
34+
OpenAIAgentsTracingInterceptor,
35+
)
36+
from temporalio.contrib.pydantic import pydantic_data_converter
37+
from temporalio.worker import Worker, WorkerConfig
2938

3039

3140
@contextmanager
@@ -133,3 +142,121 @@ def stream_response(
133142
) -> AsyncIterator[TResponseStreamEvent]:
134143
"""Get a streamed response from the model. Unimplemented."""
135144
raise NotImplementedError()
145+
146+
147+
class OpenAIAgentsPlugin(temporalio.client.Plugin, temporalio.worker.Plugin):
148+
"""Temporal plugin for integrating OpenAI agents with Temporal workflows.
149+
150+
.. warning::
151+
This class is experimental and may change in future versions.
152+
Use with caution in production environments.
153+
154+
This plugin provides seamless integration between the OpenAI Agents SDK and
155+
Temporal workflows. It automatically configures the necessary interceptors,
156+
activities, and data converters to enable OpenAI agents to run within
157+
Temporal workflows with proper tracing and model execution.
158+
159+
The plugin:
160+
1. Configures the Pydantic data converter for type-safe serialization
161+
2. Sets up tracing interceptors for OpenAI agent interactions
162+
3. Registers model execution activities
163+
4. Manages the OpenAI agent runtime overrides during worker execution
164+
165+
Args:
166+
model_params: Configuration parameters for Temporal activity execution
167+
of model calls. If None, default parameters will be used.
168+
model_provider: Optional model provider for custom model implementations.
169+
Useful for testing or custom model integrations.
170+
171+
Example:
172+
>>> from temporalio.client import Client
173+
>>> from temporalio.worker import Worker
174+
>>> from temporalio.contrib.openai_agents import OpenAIAgentsPlugin, ModelActivityParameters
175+
>>> from datetime import timedelta
176+
>>>
177+
>>> # Configure model parameters
178+
>>> model_params = ModelActivityParameters(
179+
... start_to_close_timeout=timedelta(seconds=30),
180+
... retry_policy=RetryPolicy(maximum_attempts=3)
181+
... )
182+
>>>
183+
>>> # Create plugin
184+
>>> plugin = OpenAIAgentsPlugin(model_params=model_params)
185+
>>>
186+
>>> # Use with client and worker
187+
>>> client = await Client.connect(
188+
... "localhost:7233",
189+
... plugins=[plugin]
190+
... )
191+
>>> worker = Worker(
192+
... client,
193+
... task_queue="my-task-queue",
194+
... workflows=[MyWorkflow],
195+
... )
196+
"""
197+
198+
def __init__(
199+
self,
200+
model_params: Optional[ModelActivityParameters] = None,
201+
model_provider: Optional[ModelProvider] = None,
202+
) -> None:
203+
"""Initialize the OpenAI agents plugin.
204+
205+
Args:
206+
model_params: Configuration parameters for Temporal activity execution
207+
of model calls. If None, default parameters will be used.
208+
model_provider: Optional model provider for custom model implementations.
209+
Useful for testing or custom model integrations.
210+
"""
211+
self._model_params = model_params
212+
self._model_provider = model_provider
213+
214+
def configure_client(self, config: ClientConfig) -> ClientConfig:
215+
"""Configure the Temporal client for OpenAI agents integration.
216+
217+
This method sets up the Pydantic data converter to enable proper
218+
serialization of OpenAI agent objects and responses.
219+
220+
Args:
221+
config: The client configuration to modify.
222+
223+
Returns:
224+
The modified client configuration.
225+
"""
226+
config["data_converter"] = pydantic_data_converter
227+
return super().configure_client(config)
228+
229+
def configure_worker(self, config: WorkerConfig) -> WorkerConfig:
230+
"""Configure the Temporal worker for OpenAI agents integration.
231+
232+
This method adds the necessary interceptors and activities for OpenAI
233+
agent execution:
234+
- Adds tracing interceptors for OpenAI agent interactions
235+
- Registers model execution activities
236+
237+
Args:
238+
config: The worker configuration to modify.
239+
240+
Returns:
241+
The modified worker configuration.
242+
"""
243+
config["interceptors"] = list(config.get("interceptors") or []) + [
244+
OpenAIAgentsTracingInterceptor()
245+
]
246+
config["activities"] = list(config.get("activities") or []) + [
247+
ModelActivity(self._model_provider).invoke_model_activity
248+
]
249+
return super().configure_worker(config)
250+
251+
async def run_worker(self, worker: Worker) -> None:
252+
"""Run the worker with OpenAI agents temporal overrides.
253+
254+
This method sets up the necessary runtime overrides for OpenAI agents
255+
to work within the Temporal worker context, including custom runners
256+
and trace providers.
257+
258+
Args:
259+
worker: The worker instance to run.
260+
"""
261+
with set_open_ai_agent_temporal_overrides(self._model_params):
262+
await super().run_worker(worker)

temporalio/contrib/openai_agents/workflow.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,4 +240,26 @@ async def run_operation(ctx: RunContextWrapper[Any], input: str) -> Any:
240240

241241

242242
class ToolSerializationError(TemporalError):
243-
"""Error that occurs when a tool output could not be serialized."""
243+
"""Error that occurs when a tool output could not be serialized.
244+
245+
.. warning::
246+
This exception is experimental and may change in future versions.
247+
Use with caution in production environments.
248+
249+
This exception is raised when a tool (created from an activity or Nexus operation)
250+
returns a value that cannot be properly serialized for use by the OpenAI agent.
251+
All tool outputs must be convertible to strings for the agent to process them.
252+
253+
The error typically occurs when:
254+
- A tool returns a complex object that doesn't have a meaningful string representation
255+
- The returned object cannot be converted using str()
256+
- Custom serialization is needed but not implemented
257+
258+
Example:
259+
>>> @activity.defn
260+
>>> def problematic_tool() -> ComplexObject:
261+
... return ComplexObject() # This might cause ToolSerializationError
262+
263+
To fix this error, ensure your tool returns string-convertible values or
264+
modify the tool to return a string representation of the result.
265+
"""

0 commit comments

Comments
 (0)