Skip to content

Commit 61af3ea

Browse files
authored
💥 Streamline OpenAI module layout (#947)
* WIP module layout overhaul * WIP. Mostly test model changes * Finish decorator definition and docstrings * Also tag activity_as_tool as a temporal tool * Remove temporal only tool restriction * Rearrange imports * Fix 3.9 typing * Addressing comments * Updating readme * Remove ellipsis * Fix linting * Reduce research workflow quantity, extend timeout
1 parent 3be6b80 commit 61af3ea

14 files changed

+359
-364
lines changed

‎temporalio/contrib/openai_agents/README.md

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,7 @@ The first file, `hello_world_workflow.py`, defines an OpenAI agent within a Temp
5656
```python
5757
# File: hello_world_workflow.py
5858
from temporalio import workflow
59-
60-
# Trusted imports bypass the Temporal sandbox, which otherwise
61-
# prevents imports which may result in non-deterministic execution.
62-
with workflow.unsafe.imports_passed_through():
63-
from agents import Agent, Runner
59+
from agents import Agent, Runner
6460

6561
@workflow.defn
6662
class HelloWorldAgent:
@@ -80,11 +76,6 @@ We annotate the `HelloWorldAgent` class with `@workflow.defn` to define a workfl
8076
We use the `Agent` class to define a simple agent, one which always responds with haikus.
8177
Within the workflow, we start the agent using the `Runner`, as is typical, passing through `prompt` as an argument.
8278

83-
Perhaps the most interesting thing about this code is the `workflow.unsafe.imports_passed_through()` context manager that precedes the OpenAI Agents SDK imports.
84-
This statement tells Temporal to skip sandboxing for these trusted libraries.
85-
This is important because Python's dynamic nature forces Temporal's Python's sandbox to re-validate imports every time a workflow runs, which comes at a performance cost.
86-
The OpenAI Agents SDK also contains certain code that Temporal is not able to validate automatically for determinism.
87-
8879
The second file, `run_worker.py`, launches a Temporal worker.
8980
This is a program that connects to the Temporal server and receives work to run, in this case `HelloWorldAgent` invocations.
9081

@@ -95,14 +86,13 @@ import asyncio
9586
from datetime import timedelta
9687

9788
from temporalio.client import Client
98-
from temporalio.contrib.openai_agents.invoke_model_activity import ModelActivity
99-
from temporalio.contrib.openai_agents.model_parameters import ModelActivityParameters
100-
from temporalio.contrib.openai_agents.open_ai_data_converter import open_ai_data_converter
101-
from temporalio.contrib.openai_agents.temporal_openai_agents import set_open_ai_agent_temporal_overrides
89+
from temporalio.contrib.openai_agents import ModelActivity, ModelActivityParameters, set_open_ai_agent_temporal_overrides
90+
from temporalio.contrib.pydantic import pydantic_data_converter
10291
from temporalio.worker import Worker
10392

10493
from hello_world_workflow import HelloWorldAgent
10594

95+
10696
async def worker_main():
10797
# Configure the OpenAI Agents SDK to use Temporal activities for LLM API calls
10898
# and for tool calls.
@@ -114,26 +104,26 @@ async def worker_main():
114104
# Use the OpenAI data converter to ensure proper serialization/deserialization
115105
client = await Client.connect(
116106
"localhost:7233",
117-
data_converter=open_ai_data_converter,
107+
data_converter=pydantic_data_converter,
118108
)
119109

120-
model_activity = ModelActivity(model_provider=None)
121-
worker = Worker(
122-
client,
123-
task_queue="my-task-queue",
124-
workflows=[HelloWorldAgent],
125-
activities=[model_activity.invoke_model_activity],
126-
)
127-
await worker.run()
110+
worker = Worker(
111+
client,
112+
task_queue="my-task-queue",
113+
workflows=[HelloWorldAgent],
114+
activities=[ModelActivity().invoke_model_activity],
115+
)
116+
await worker.run()
117+
128118

129119
if __name__ == "__main__":
130120
asyncio.run(worker_main())
131121
```
132122

133123
We wrap the entire `worker_main` function body in the `set_open_ai_agent_temporal_overrides()` context manager.
134124
This causes a Temporal activity to be invoked whenever the OpenAI Agents SDK invokes an LLM or calls a tool.
135-
We also pass the `open_ai_data_converter` to the Temporal Client, which ensures proper serialization of OpenAI Agents SDK data.
136-
We create a `ModelActivity` which serves as a generic wrapper for LLM calls, and we register this wrapper's invocation point, `model_activity.invoke_model_activity`, with the worker.
125+
We also pass the `pydantic_data_converter` to the Temporal Client, which ensures proper serialization of pydantic models in OpenAI Agents SDK data.
126+
We create a `ModelActivity` which serves as a generic wrapper for LLM calls, and we register this wrapper's invocation point, `ModelActivity().invoke_model_activity`, with the worker.
137127

138128
In order to launch the agent, use the standard Temporal workflow invocation:
139129

@@ -144,15 +134,15 @@ import asyncio
144134

145135
from temporalio.client import Client
146136
from temporalio.common import WorkflowIDReusePolicy
147-
from temporalio.contrib.openai_agents.open_ai_data_converter import open_ai_data_converter
137+
from temporalio.contrib.pydantic import pydantic_data_converter
148138

149139
from hello_world_workflow import HelloWorldAgent
150140

151141
async def main():
152142
# Create client connected to server at the given address
153143
client = await Client.connect(
154144
"localhost:7233",
155-
data_converter=open_ai_data_converter,
145+
data_converter=pydantic_data_converter,
156146
)
157147

158148
# Execute a workflow
@@ -171,7 +161,7 @@ if __name__ == "__main__":
171161

172162
This launcher script executes the Temporal workflow to start the agent.
173163

174-
Note that this basic example works without providing the `open_ai_data_converter` to the Temporal client that executes the workflow, but we include it because more complex uses will generally need it.
164+
Note that this basic example works without providing the `pydantic_data_converter` to the Temporal client that executes the workflow, but we include it because more complex uses will generally need it.
175165

176166

177167
## Using Temporal Activities as OpenAI Agents Tools
@@ -186,10 +176,8 @@ We then pass this through the `activity_as_tool` helper function to create an Op
186176
from dataclasses import dataclass
187177
from datetime import timedelta
188178
from temporalio import activity, workflow
189-
from temporalio.contrib.openai_agents.temporal_tools import activity_as_tool
190-
191-
with workflow.unsafe.imports_passed_through():
192-
from agents import Agent, Runner
179+
from temporalio.contrib import openai_agents
180+
from agents import Agent, Runner
193181

194182
@dataclass
195183
class Weather:
@@ -210,7 +198,7 @@ class WeatherAgent:
210198
name="Weather Assistant",
211199
instructions="You are a helpful weather agent.",
212200
tools=[
213-
activity_as_tool(
201+
openai_agents.workflow.activity_as_tool(
214202
get_weather,
215203
start_to_close_timeout=timedelta(seconds=10)
216204
)

‎temporalio/contrib/openai_agents/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,25 @@
77
This module is experimental and may change in future versions.
88
Use with caution in production environments.
99
"""
10+
11+
from temporalio.contrib.openai_agents._invoke_model_activity import ModelActivity
12+
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
13+
from temporalio.contrib.openai_agents._trace_interceptor import (
14+
OpenAIAgentsTracingInterceptor,
15+
)
16+
from temporalio.contrib.openai_agents.temporal_openai_agents import (
17+
TestModel,
18+
TestModelProvider,
19+
set_open_ai_agent_temporal_overrides,
20+
workflow,
21+
)
22+
23+
__all__ = [
24+
"ModelActivity",
25+
"ModelActivityParameters",
26+
"workflow",
27+
"set_open_ai_agent_temporal_overrides",
28+
"OpenAIAgentsTracingInterceptor",
29+
"TestModel",
30+
"TestModelProvider",
31+
]

‎temporalio/contrib/openai_agents/_openai_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
from temporalio import workflow
1717
from temporalio.common import Priority, RetryPolicy
18+
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
1819
from temporalio.contrib.openai_agents._temporal_model_stub import _TemporalModelStub
19-
from temporalio.contrib.openai_agents.model_parameters import ModelActivityParameters
2020
from temporalio.workflow import ActivityCancellationType, VersioningIntent
2121

2222

‎temporalio/contrib/openai_agents/_temporal_model_stub.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from temporalio import workflow
88
from temporalio.common import Priority, RetryPolicy
9-
from temporalio.contrib.openai_agents.model_parameters import ModelActivityParameters
9+
from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters
1010
from temporalio.workflow import ActivityCancellationType, VersioningIntent
1111

1212
logger = logging.getLogger(__name__)
@@ -31,7 +31,7 @@
3131
from agents.items import TResponseStreamEvent
3232
from openai.types.responses.response_prompt_param import ResponsePromptParam
3333

34-
from temporalio.contrib.openai_agents.invoke_model_activity import (
34+
from temporalio.contrib.openai_agents._invoke_model_activity import (
3535
ActivityModelInput,
3636
AgentOutputSchemaInput,
3737
FunctionToolInput,

‎temporalio/contrib/openai_agents/open_ai_data_converter.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)