55
66PATTERN 1: Simple External Tools as Activities (activity_as_tool) 
77- Convert individual Temporal activities directly into agent tools 
8- - 1:1 mapping between tool calls and activities   
8+ - 1:1 mapping between tool calls and activities 
99- Best for: single non-deterministic operations (API calls, DB queries) 
1010- Example: get_weather activity → weather tool 
1111
1919
2020WHY THIS APPROACH IS GAME-CHANGING: 
2121=================================== 
22- There's a crucial meta-point that should be coming through here: **why is this different?**   
23- This approach is truly transactional because of how the `await` works in Temporal workflows.   
24- Consider a "move money" example - if the operation fails between the withdraw and deposit,   
25- Temporal will resume exactly where it left off - the agent gets real-world flexibility even   
22+ There's a crucial meta-point that should be coming through here: **why is this different?** 
23+ This approach is truly transactional because of how the `await` works in Temporal workflows. 
24+ Consider a "move money" example - if the operation fails between the withdraw and deposit, 
25+ Temporal will resume exactly where it left off - the agent gets real-world flexibility even 
2626if systems die. 
2727
28- **Why even use Temporal? Why are we adding complexity?** The gain is enormous when you   
28+ **Why even use Temporal? Why are we adding complexity?** The gain is enormous when you 
2929consider what happens without it: 
3030
31- In a traditional approach without Temporal, if you withdraw money but then the system crashes   
32- before depositing, you're stuck in a broken state. The money has been withdrawn, but never   
33- deposited. In a banking scenario, you can't just "withdraw again" - the money is already gone   
31+ In a traditional approach without Temporal, if you withdraw money but then the system crashes 
32+ before depositing, you're stuck in a broken state. The money has been withdrawn, but never 
33+ deposited. In a banking scenario, you can't just "withdraw again" - the money is already gone 
3434from the source account, and your agent has no way to recover or know what state it was in. 
3535
36- This is why you can't build very complicated agents without this confidence in transactional   
36+ This is why you can't build very complicated agents without this confidence in transactional 
3737behavior. Temporal gives us: 
3838
3939- **Guaranteed execution**: If the workflow starts, it will complete, even through failures 
4040- **Exact resumption**: Pick up exactly where we left off, not start over 
41- - **Transactional integrity**: Either both operations complete, or the workflow can be designed   
41+ - **Transactional integrity**: Either both operations complete, or the workflow can be designed 
4242 to handle partial completion 
4343- **Production reliability**: Build agents that can handle real-world complexity and failures 
4444
45- Without this foundation, agents remain fragile toys. With Temporal, they become production-ready   
45+ Without this foundation, agents remain fragile toys. With Temporal, they become production-ready 
4646systems that can handle the complexities of the real world. 
4747""" 
4848
7272
7373logger  =  make_logger (__name__ )
7474
75+ 
7576@workflow .defn (name = environment_variables .WORKFLOW_NAME ) 
7677class  ExampleTutorialWorkflow (BaseWorkflow ):
7778 """ 
7879 Minimal async workflow template for AgentEx Temporal agents. 
7980 """ 
81+ 
8082 def  __init__ (self ):
8183 super ().__init__ (display_name = environment_variables .AGENT_NAME )
8284 self ._complete_task  =  False 
@@ -85,35 +87,35 @@ def __init__(self):
8587 @workflow .signal (name = SignalName .RECEIVE_EVENT ) 
8688 async  def  on_task_event_send (self , params : SendEventParams ) ->  None :
8789 logger .info (f"Received task message instruction: { params }  " )
88-   
89-  # Echo back the client's message to show it in the UI. This is not done by default   
90+ 
91+  # Echo back the client's message to show it in the UI. This is not done by default 
9092 # so the agent developer has full control over what is shown to the user. 
9193 await  adk .messages .create (task_id = params .task .id , content = params .event .content )
9294
9395 # ============================================================================ 
9496 # OpenAI Agents SDK + Temporal Integration: Two Patterns for Tool Creation 
9597 # ============================================================================ 
96-   
98+ 
9799 # #### When to Use Activities for Tools 
98100 # 
99101 # You'll want to use the activity pattern for tools in the following scenarios: 
100102 # 
101-  # - **API calls within the tool**: Whenever your tool makes an API call (external   
102-  # service, database, etc.), you must wrap it as an activity since these are   
103+  # - **API calls within the tool**: Whenever your tool makes an API call (external 
104+  # service, database, etc.), you must wrap it as an activity since these are 
103105 # non-deterministic operations that could fail or return different results 
104-  # - **Idempotent single operations**: When the tool performs an already idempotent   
105-  # single call that you want to ensure gets executed reliably with Temporal's retry   
106+  # - **Idempotent single operations**: When the tool performs an already idempotent 
107+  # single call that you want to ensure gets executed reliably with Temporal's retry 
106108 # guarantees 
107109 # 
108-  # Let's start with the case where it is non-deterministic. If this is the case, we   
109-  # want this tool to be an activity to guarantee that it will be executed. The way to   
110-  # do this is to add some syntax to make the tool call an activity. Let's create a tool   
111-  # that gives us the weather and create a weather agent. For this example, we will just   
112-  # return a hard-coded string but we can easily imagine this being an API call to a   
113-  # weather service which would make it non-deterministic. First we will create a new   
114-  # file called `activities.py`. Here we will create a function to get the weather and   
110+  # Let's start with the case where it is non-deterministic. If this is the case, we 
111+  # want this tool to be an activity to guarantee that it will be executed. The way to 
112+  # do this is to add some syntax to make the tool call an activity. Let's create a tool 
113+  # that gives us the weather and create a weather agent. For this example, we will just 
114+  # return a hard-coded string but we can easily imagine this being an API call to a 
115+  # weather service which would make it non-deterministic. First we will create a new 
116+  # file called `activities.py`. Here we will create a function to get the weather and 
115117 # simply add an activity annotation on top. 
116-   
118+ 
117119 # There are TWO key patterns for integrating tools with the OpenAI Agents SDK in Temporal: 
118120 # 
119121 # PATTERN 1: Simple External Tools as Activities 
@@ -147,7 +149,7 @@ async def on_task_event_send(self, params: SendEventParams) -> None:
147149 # The get_weather activity will be executed with durability guarantees 
148150 activity_as_tool (
149151 get_weather , # This is defined in activities.py as @activity.defn 
150-  start_to_close_timeout = timedelta (seconds = 10 )
152+  start_to_close_timeout = timedelta (seconds = 10 ), 
151153 ),
152154 ],
153155 )
@@ -156,7 +158,7 @@ async def on_task_event_send(self, params: SendEventParams) -> None:
156158 result  =  await  Runner .run (weather_agent , params .event .content .content )
157159
158160 # ============================================================================ 
159-  # PATTERN 2: Multiple Activities Within Tools   
161+  # PATTERN 2: Multiple Activities Within Tools 
160162 # ============================================================================ 
161163 # Use this pattern when: 
162164 # - You need multiple sequential non-deterministic operations within one tool 
@@ -171,7 +173,7 @@ async def on_task_event_send(self, params: SendEventParams) -> None:
171173 # 
172174 # BENEFITS: 
173175 # - Guaranteed execution order (withdraw THEN deposit) 
174-  # - Each step is durable and retryable individually   
176+  # - Each step is durable and retryable individually 
175177 # - Atomic operations from the agent's perspective 
176178 # - Better than having LLM make multiple separate tool calls 
177179
@@ -186,7 +188,7 @@ async def on_task_event_send(self, params: SendEventParams) -> None:
186188 # move_money, 
187189 # ], 
188190 # ) 
189-   
191+ 
190192 # # Run the agent - when it calls move_money tool, it will create TWO activities: 
191193 # # 1. withdraw_money activity 
192194 # # 2. deposit_money activity (only after withdraw succeeds) 
@@ -195,17 +197,17 @@ async def on_task_event_send(self, params: SendEventParams) -> None:
195197 # ============================================================================ 
196198 # PATTERN COMPARISON SUMMARY: 
197199 # ============================================================================ 
198-  #   
200+  # 
199201 # Pattern 1 (activity_as_tool): | Pattern 2 (function_tool with activities): 
200202 # - Single activity per tool call | - Multiple activities per tool call 
201-  # - 1:1 tool to activity mapping | - 1:many tool to activity mapping   
203+  # - 1:1 tool to activity mapping | - 1:many tool to activity mapping 
202204 # - Simple non-deterministic ops | - Complex multi-step operations 
203205 # - Let LLM sequence multiple tools | - Code controls activity sequencing 
204206 # - Example: get_weather, db_lookup | - Example: money_transfer, multi_step_workflow 
205207 # 
206208 # BOTH patterns provide: 
207209 # - Automatic retries and failure recovery 
208-  # - Full observability in Temporal UI   
210+  # - Full observability in Temporal UI 
209211 # - Durable execution guarantees 
210212 # - Seamless integration with OpenAI Agents SDK 
211213 # ============================================================================ 
@@ -234,11 +236,12 @@ async def on_task_create(self, params: CreateTaskParams) -> str:
234236
235237 await  workflow .wait_condition (
236238 lambda : self ._complete_task ,
237-  timeout = None , # Set a timeout if you want to prevent the task from running indefinitely. Generally this is not needed. Temporal can run hundreds of millions of workflows in parallel and more. Only do this if you have a specific reason to do so. 
239+  timeout = None ,   # Set a timeout if you want to prevent the task from running indefinitely. Generally this is not needed. Temporal can run hundreds of millions of workflows in parallel and more. Only do this if you have a specific reason to do so. 
238240 )
239241 return  "Task completed" 
240242
241243 @workflow .signal  
242244 async  def  fulfill_order_signal (self , success : bool ) ->  None :
243245 if  success  ==  True :
244-  await  self ._pending_confirmation .put (True )
246+  await  self ._pending_confirmation .put (True )
247+ 
0 commit comments