|
1 | 1 | """Initialize Temporal OpenAI Agents overrides.""" |
2 | 2 |
|
3 | | -import json |
4 | 3 | from contextlib import contextmanager |
5 | | -from datetime import timedelta |
6 | | -from typing import Any, AsyncIterator, Callable, Optional, Type, Union |
| 4 | +from typing import AsyncIterator, Callable, Optional, Union |
7 | 5 |
|
8 | | -import nexusrpc |
9 | 6 | from agents import ( |
10 | 7 | AgentOutputSchemaBase, |
11 | 8 | Handoff, |
|
14 | 11 | ModelResponse, |
15 | 12 | ModelSettings, |
16 | 13 | ModelTracing, |
17 | | - RunContextWrapper, |
18 | 14 | Tool, |
19 | 15 | TResponseInputItem, |
20 | 16 | set_trace_provider, |
21 | 17 | ) |
22 | | -from agents.function_schema import function_schema |
23 | 18 | from agents.items import TResponseStreamEvent |
24 | 19 | from agents.run import get_default_agent_runner, set_default_agent_runner |
25 | | -from agents.tool import ( |
26 | | - FunctionTool, |
27 | | -) |
28 | 20 | from agents.tracing import get_trace_provider |
29 | 21 | from agents.tracing.provider import DefaultTraceProvider |
30 | 22 | from openai.types.responses import ResponsePromptParam |
31 | 23 |
|
32 | | -from temporalio import activity |
33 | | -from temporalio import workflow as temporal_workflow |
34 | | -from temporalio.common import Priority, RetryPolicy |
35 | 24 | from temporalio.contrib.openai_agents._model_parameters import ModelActivityParameters |
36 | 25 | from temporalio.contrib.openai_agents._openai_runner import TemporalOpenAIRunner |
37 | 26 | from temporalio.contrib.openai_agents._temporal_trace_provider import ( |
38 | 27 | TemporalTraceProvider, |
39 | 28 | ) |
40 | | -from temporalio.exceptions import ApplicationError, TemporalError |
41 | | -from temporalio.workflow import ActivityCancellationType, VersioningIntent |
42 | 29 |
|
43 | 30 |
|
44 | 31 | @contextmanager |
@@ -146,204 +133,3 @@ def stream_response( |
146 | 133 | ) -> AsyncIterator[TResponseStreamEvent]: |
147 | 134 | """Get a streamed response from the model. Unimplemented.""" |
148 | 135 | raise NotImplementedError() |
149 | | - |
150 | | - |
151 | | -class ToolSerializationError(TemporalError): |
152 | | - """Error that occurs when a tool output could not be serialized.""" |
153 | | - |
154 | | - |
155 | | -class workflow: |
156 | | - """Encapsulates workflow specific primitives for working with the OpenAI Agents SDK in a workflow context""" |
157 | | - |
158 | | - @classmethod |
159 | | - def activity_as_tool( |
160 | | - cls, |
161 | | - fn: Callable, |
162 | | - *, |
163 | | - task_queue: Optional[str] = None, |
164 | | - schedule_to_close_timeout: Optional[timedelta] = None, |
165 | | - schedule_to_start_timeout: Optional[timedelta] = None, |
166 | | - start_to_close_timeout: Optional[timedelta] = None, |
167 | | - heartbeat_timeout: Optional[timedelta] = None, |
168 | | - retry_policy: Optional[RetryPolicy] = None, |
169 | | - cancellation_type: ActivityCancellationType = ActivityCancellationType.TRY_CANCEL, |
170 | | - activity_id: Optional[str] = None, |
171 | | - versioning_intent: Optional[VersioningIntent] = None, |
172 | | - summary: Optional[str] = None, |
173 | | - priority: Priority = Priority.default, |
174 | | - ) -> Tool: |
175 | | - """Convert a single Temporal activity function to an OpenAI agent tool. |
176 | | -
|
177 | | - .. warning:: |
178 | | - This API is experimental and may change in future versions. |
179 | | - Use with caution in production environments. |
180 | | -
|
181 | | - This function takes a Temporal activity function and converts it into an |
182 | | - OpenAI agent tool that can be used by the agent to execute the activity |
183 | | - during workflow execution. The tool will automatically handle the conversion |
184 | | - of inputs and outputs between the agent and the activity. Note that if you take a context, |
185 | | - mutation will not be persisted, as the activity may not be running in the same location. |
186 | | -
|
187 | | - Args: |
188 | | - fn: A Temporal activity function to convert to a tool. |
189 | | - For other arguments, refer to :py:mod:`workflow` :py:meth:`start_activity` |
190 | | -
|
191 | | - Returns: |
192 | | - An OpenAI agent tool that wraps the provided activity. |
193 | | -
|
194 | | - Raises: |
195 | | - ApplicationError: If the function is not properly decorated as a Temporal activity. |
196 | | -
|
197 | | - Example: |
198 | | - >>> @activity.defn |
199 | | - >>> def process_data(input: str) -> str: |
200 | | - ... return f"Processed: {input}" |
201 | | - >>> |
202 | | - >>> # Create tool with custom activity options |
203 | | - >>> tool = activity_as_tool( |
204 | | - ... process_data, |
205 | | - ... start_to_close_timeout=timedelta(seconds=30), |
206 | | - ... retry_policy=RetryPolicy(maximum_attempts=3), |
207 | | - ... heartbeat_timeout=timedelta(seconds=10) |
208 | | - ... ) |
209 | | - >>> # Use tool with an OpenAI agent |
210 | | - """ |
211 | | - ret = activity._Definition.from_callable(fn) |
212 | | - if not ret: |
213 | | - raise ApplicationError( |
214 | | - "Bare function without tool and activity decorators is not supported", |
215 | | - "invalid_tool", |
216 | | - ) |
217 | | - schema = function_schema(fn) |
218 | | - |
219 | | - async def run_activity(ctx: RunContextWrapper[Any], input: str) -> Any: |
220 | | - try: |
221 | | - json_data = json.loads(input) |
222 | | - except Exception as e: |
223 | | - raise ApplicationError( |
224 | | - f"Invalid JSON input for tool {schema.name}: {input}" |
225 | | - ) from e |
226 | | - |
227 | | - # Activities don't support keyword only arguments, so we can ignore the kwargs_dict return |
228 | | - args, _ = schema.to_call_args(schema.params_pydantic_model(**json_data)) |
229 | | - |
230 | | - # Add the context to the arguments if it takes that |
231 | | - if schema.takes_context: |
232 | | - args = [ctx] + args |
233 | | - |
234 | | - result = await temporal_workflow.execute_activity( |
235 | | - fn, |
236 | | - args=args, |
237 | | - task_queue=task_queue, |
238 | | - schedule_to_close_timeout=schedule_to_close_timeout, |
239 | | - schedule_to_start_timeout=schedule_to_start_timeout, |
240 | | - start_to_close_timeout=start_to_close_timeout, |
241 | | - heartbeat_timeout=heartbeat_timeout, |
242 | | - retry_policy=retry_policy, |
243 | | - cancellation_type=cancellation_type, |
244 | | - activity_id=activity_id, |
245 | | - versioning_intent=versioning_intent, |
246 | | - summary=summary, |
247 | | - priority=priority, |
248 | | - ) |
249 | | - try: |
250 | | - return str(result) |
251 | | - except Exception as e: |
252 | | - raise ToolSerializationError( |
253 | | - "You must return a string representation of the tool output, or something we can call str() on" |
254 | | - ) from e |
255 | | - |
256 | | - return FunctionTool( |
257 | | - name=schema.name, |
258 | | - description=schema.description or "", |
259 | | - params_json_schema=schema.params_json_schema, |
260 | | - on_invoke_tool=run_activity, |
261 | | - strict_json_schema=True, |
262 | | - ) |
263 | | - |
264 | | - @classmethod |
265 | | - def nexus_operation_as_tool( |
266 | | - cls, |
267 | | - operation: nexusrpc.Operation[Any, Any], |
268 | | - *, |
269 | | - service: Type[Any], |
270 | | - endpoint: str, |
271 | | - schedule_to_close_timeout: Optional[timedelta] = None, |
272 | | - ) -> Tool: |
273 | | - """Convert a Nexus operation into an OpenAI agent tool. |
274 | | -
|
275 | | - .. warning:: |
276 | | - This API is experimental and may change in future versions. |
277 | | - Use with caution in production environments. |
278 | | -
|
279 | | - This function takes a Nexus operation and converts it into an |
280 | | - OpenAI agent tool that can be used by the agent to execute the operation |
281 | | - during workflow execution. The tool will automatically handle the conversion |
282 | | - of inputs and outputs between the agent and the operation. |
283 | | -
|
284 | | - Args: |
285 | | - fn: A Nexus operation to convert into a tool. |
286 | | - service: The Nexus service class that contains the operation. |
287 | | - endpoint: The Nexus endpoint to use for the operation. |
288 | | -
|
289 | | - Returns: |
290 | | - An OpenAI agent tool that wraps the provided operation. |
291 | | -
|
292 | | - Example: |
293 | | - >>> @nexusrpc.service |
294 | | - ... class WeatherService: |
295 | | - ... get_weather_object_nexus_operation: nexusrpc.Operation[WeatherInput, Weather] |
296 | | - >>> |
297 | | - >>> # Create tool with custom activity options |
298 | | - >>> tool = nexus_operation_as_tool( |
299 | | - ... WeatherService.get_weather_object_nexus_operation, |
300 | | - ... service=WeatherService, |
301 | | - ... endpoint="weather-service", |
302 | | - ... ) |
303 | | - >>> # Use tool with an OpenAI agent |
304 | | - """ |
305 | | - |
306 | | - def operation_callable(input): |
307 | | - raise NotImplementedError("This function definition is used as a type only") |
308 | | - |
309 | | - operation_callable.__annotations__ = { |
310 | | - "input": operation.input_type, |
311 | | - "return": operation.output_type, |
312 | | - } |
313 | | - operation_callable.__name__ = operation.name |
314 | | - |
315 | | - schema = function_schema(operation_callable) |
316 | | - |
317 | | - async def run_operation(ctx: RunContextWrapper[Any], input: str) -> Any: |
318 | | - try: |
319 | | - json_data = json.loads(input) |
320 | | - except Exception as e: |
321 | | - raise ApplicationError( |
322 | | - f"Invalid JSON input for tool {schema.name}: {input}" |
323 | | - ) from e |
324 | | - |
325 | | - nexus_client = temporal_workflow.create_nexus_client( |
326 | | - service=service, endpoint=endpoint |
327 | | - ) |
328 | | - args, _ = schema.to_call_args(schema.params_pydantic_model(**json_data)) |
329 | | - assert len(args) == 1, "Nexus operations must have exactly one argument" |
330 | | - [arg] = args |
331 | | - result = await nexus_client.execute_operation( |
332 | | - operation, |
333 | | - arg, |
334 | | - schedule_to_close_timeout=schedule_to_close_timeout, |
335 | | - ) |
336 | | - try: |
337 | | - return str(result) |
338 | | - except Exception as e: |
339 | | - raise ToolSerializationError( |
340 | | - "You must return a string representation of the tool output, or something we can call str() on" |
341 | | - ) from e |
342 | | - |
343 | | - return FunctionTool( |
344 | | - name=schema.name, |
345 | | - description=schema.description or "", |
346 | | - params_json_schema=schema.params_json_schema, |
347 | | - on_invoke_tool=run_operation, |
348 | | - strict_json_schema=True, |
349 | | - ) |
0 commit comments