diff --git a/temporalio/contrib/openai_agents/README.md b/temporalio/contrib/openai_agents/README.md index 7c0a4c5ef..d28ceb7b5 100644 --- a/temporalio/contrib/openai_agents/README.md +++ b/temporalio/contrib/openai_agents/README.md @@ -1,60 +1,35 @@ -# OpenAI Agents SDK Support +# OpenAI Agents SDK Integration for Temporal -⚠️ **Experimental** - This module is not yet stable and may change in the future. +⚠️ **Public Preview** - The interface to this module is subject to change prior to General Availability. +We welcome questions and feedback in the [#python-sdk](https://temporalio.slack.com/archives/CTT84RS0P) Slack channel at [temporalio.slack.com](https://temporalio.slack.com/). -For questions, please join the [#python-sdk](https://temporalio.slack.com/archives/CTT84RS0P) Slack channel at [temporalio.slack.com](https://temporalio.slack.com/). -This module provides a bridge between Temporal durable execution and the [OpenAI Agents SDK](https://github.com/openai/openai-agents-python). +## Introduction -## Background +This integration combines [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) with [Temporal's durable execution](https://docs.temporal.io/evaluate/understanding-temporal#durable-execution). +It allows you to build durable agents that never lose their progress and handle long-running, asynchronous, and human-in-the-loop workflows with production-grade reliability. -If you want to build production-ready AI agents quickly, you can use this module to combine [Temporal durable execution](https://docs.temporal.io/evaluate/understanding-temporal#durable-execution) with OpenAI Agents. -Temporal's durable execution provides a crash-proof system foundation, and OpenAI Agents offers a lightweight and yet powerful framework for defining agent functionality. +Temporal and OpenAI Agents SDK are complementary technologies, both of which contribute to simplifying what it takes to build highly capable, high-quality AI systems. +Temporal provides a crash-proof system foundation, taking care of the distributed systems challenges inherent to production agentic systems. +OpenAI Agents SDK offers a lightweight yet powerful framework for defining those agents. +This document is organized as follows: + - **[Hello World Durable Agent](#hello-world-durable-agent).** Your first durable agent example. + - **[Background Concepts](#core-concepts).** Background on durable execution and AI agents. + - **[Full Example](#full-example)** Running the Hello World Durable Agent example. + - **[Tool Calling](#tool-calling).** Calling agent Tools in Temporal. + - **[Feature Support](#feature-support).** Compatibility matrix. -## Approach +The [samples repository](https://github.com/temporalio/samples-python/tree/main/openai_agents) contains examples including basic usage, common agent patterns, and more complete samples. -The standard control flow of a single AI agent involves: -1. Receiving *input* and handing it to an *LLM*. -2. At the direction of the LLM, calling *tools*, and returning that output back to the LLM. -3. Repeating as necessary, until the LLM produces *output*. +## Hello World Durable Agent -The diagram below illustrates an AI agent control flow. +The code below shows how to wrap an agent for durable execution. -```mermaid -graph TD - A["INPUT"] --> B["LLM"] - B <--> C["TOOLS"] - B --> D["OUTPUT"] -``` - -To provide durable execution, Temporal needs to be able to recover from failures at any step of this process. -To do this, Temporal requires separating an application's deterministic (repeatable) and non-deterministic parts: - -1. Deterministic pieces, termed *workflows*, execute the same way if re-run with the same inputs. -2. Non-deterministic pieces, termed *activities*, have no limitations—they may perform I/O and any other operations. - -Temporal maintains a server-side execution history of all state state passing in and out of a workflow, using it to recover when needed. -See the [Temporal documentation](https://docs.temporal.io/evaluate/understanding-temporal#temporal-application-the-building-blocks) for more information. - -How do we apply the Temporal execution model to enable durable execution for AI agents? - -- The core control flow, which is managed by the OpenAI Agents SDK, goes into a Temporal workflow. -- Calls to the LLM provider, which are inherently non-deterministic, go into activities. -- Calls to tools, which could contain arbitrary code, similarly go into activities. - -This module ensures that LLM calls and tool calls originating from the OpenAI Agents SDK run as Temporal activities. -It also ensures that their inputs and outputs are properly serialized. - -## Basic Example - -Let's start with a simple example. - -The first file, `hello_world_workflow.py`, defines an OpenAI agent within a Temporal workflow. +### File 1: Durable Agent (`hello_world.py`) ```python -# File: hello_world_workflow.py from temporalio import workflow from agents import Agent, Runner @@ -67,17 +42,148 @@ class HelloWorldAgent: instructions="You only respond in haikus.", ) - result = await Runner.run(starting_agent=agent, input=prompt) + result = await Runner.run(agent, input=prompt) return result.final_output ``` +In this example, Temporal provides the durable execution wrapper: the `HelloWorldAgent.run` method. +The content of that method, is regular OpenAI Agents SDK code. + If you are familiar with Temporal and with Open AI Agents SDK, this code will look very familiar. -We annotate the `HelloWorldAgent` class with `@workflow.defn` to define a workflow, then use the `@workflow.run` annotation to define the entrypoint. -We use the `Agent` class to define a simple agent, one which always responds with haikus. -Within the workflow, we start the agent using the `Runner`, as is typical, passing through `prompt` as an argument. +The `@workflow.defn` annotation on the `HelloWorldAgent` indicates that this class will contain durable execution logic. The `@workflow.run` annotation defines the entry point. +We use the `Agent` class from OpenAI Agents SDK to define a simple agent, instructing it to always respond with haikus. +We then run that agent, using the `Runner` class from OpenAI Agents SDK, passing through `prompt` as an argument. + + +We will [complete this example below](#full-example). +Before digging further into the code, we will review some background that will make it easier to understand. + +## Background Concepts + +We encourage you to review this section thoroughly to gain a solid understanding of AI agents and durable execution with Temporal. +This knowledge will make it easier to design and build durable agents. +If you are already well versed in these topics, feel free to skim this section or skip ahead. + +### AI Agents + +In the OpenAI Agents SDK, an agent is an AI model configured with instructions, tools, MCP servers, guardrails, handoffs, context, and more. + +We describe each of these briefly: + +- *AI model*. An LLM such as OpenAI's GPT, Google's Gemini, or one of many others. +- *Instructions*. Also known as a system prompt, the instructions contain the initial input to the model, which configures it for the job it will do. +- *Tools*. Typically, Python functions that the model may choose to invoke. Tools are functions with text-descriptions that explain their functionality to the model. +- *MCP servers*. Best known for providing tools, MCP offers a pluggable standard for interoperability, including file-like resources, prompt templates, and human approvals. MCP servers may be accessed over the network or run in a local process. +- *Guardrails*. Checks on the input or the output of an agent to ensure compliance or safety. Guardrails may be implemented as regular code or as AI agents. +- *Handoffs*. A handoff occurs when an agent delegates a task to another agent. During a handoff the conversation history remains the same, and passes to a new agent with its own model, instructions, tools. +- *Context*. This is an overloaded term. Here, context refers to a framework object that is shared across tools and other code, but is not passed to the model. + +Now, let's see how these components work together. +In a common pattern, the model first receives user input and then reasons about which tool to invoke. +The tool's response is passed back to the model, which may call additional tools, repeating this loop until the task is complete. + +The diagram below illustrates this flow. + +```text + +-------------------+ + | User Input | + +-------------------+ + | + v + +---------------------+ + | Reasoning (Model) | <--+ + +---------------------+ | + | | + (decides which action) | + v | + +---------------------+ | + | Action | | + | (e.g., use a Tool) | | + +---------------------+ | + | | + v | + +---------------------+ | + | Observation | | + | (Tool Output) | | + +---------------------+ | + | | + +----------------+ + (loop: uses new info to reason + again, until task is complete) +``` + +Even in a simple example like this, there are many places where things can go wrong. +Tools call APIs that sometimes fail, while models can encounter rate limits, requiring retries. +The longer the agent runs, the more costly it is to start the job over. +We next describe durable execution, which handles such failures seamlessly. + +### Durable Execution + +In Temporal's durable execution implementation, a program that crashes or encounters an exception while interacting with a model or API will retry until it can successfully complete. + +Temporal relies primarily on a replay mechanism to recover from failures. +As the program makes progress, Temporal saves key inputs and decisions, allowing a re-started program to pick up right where it left off. + +The key to making this work is to separate the applications repeatable (deterministic) and non-repeatable (non-deterministic) parts: + +1. Deterministic pieces, termed *workflows*, execute the same way when re-run with the same inputs. +2. Non-deterministic pieces, termed *activities*, can run arbitrary code, performing I/O and any other operations. + +Workflow code can run for extended periods and, if interrupted, resume exactly where it left off. +Activity code faces no restrictions on I/O or external interactions, but if it fails part-way through it restarts from the beginning. + +In the AI-agent example above, model invocations and tool calls run inside activities, while the logic that coordinates them lives in the workflow. +This pattern generalizes to more sophisticated agents. +We refer to that coordinating logic as *agent orchestration*. + +As a general rule, agent orchestration code executes within the Temporal workflow, whereas model calls and any I/O-bound tool invocations execute as Temporal activities. + +The diagram below shows the overall architecture of an agentic application in Temporal. +The Temporal Server is responsible to tracking program execution and making sure associated state is preserved reliably (i.e., stored to a database, possibly replicated across cloud regions). +Temporal Server manages data in encrypted form, so all data processing occurs on the Worker, which runs the workflow and activities. + + +```text + +---------------------+ + | Temporal Server | (Stores workflow state, + +---------------------+ schedules activities, + ^ persists progress) + | + Save state, | Schedule Tasks, + progress, | load state on resume + timeouts | + | ++------------------------------------------------------+ +| Worker | +| +----------------------------------------------+ | +| | Workflow Code | | +| | (Agent Orchestration Loop) | | +| +----------------------------------------------+ | +| | | | | +| v v v | +| +-----------+ +-----------+ +-------------+ | +| | Activity | | Activity | | Activity | | +| | (Tool 1) | | (Tool 2) | | (Model API) | | +| +-----------+ +-----------+ +-------------+ | +| | | | | ++------------------------------------------------------+ + | | | + v v v + [External APIs, services, databases, etc.] +``` + + +See the [Temporal documentation](https://docs.temporal.io/evaluate/understanding-temporal#temporal-application-the-building-blocks) for more information. + + +## Complete Example + +To make the [Hello World durable agent](#hello-world-durable-agent) shown earlier available in Temporal, we need to create a worker program. +To see it run, we also need a client to launch it. +We show these files below. + -The second file, `run_worker.py`, launches a Temporal worker. -This is a program that connects to the Temporal server and receives work to run, in this case `HelloWorldAgent` invocations. +### File 2: Launch Worker (`run_worker.py`) ```python # File: run_worker.py @@ -93,18 +199,16 @@ from hello_world_workflow import HelloWorldAgent async def worker_main(): - # Configure the OpenAI Agents SDK to use Temporal activities for LLM API calls - # and for tool calls. - # Create a Temporal client connected to server at the given address + # Use the plugin to configure Temporal for use with OpenAI Agents SDK client = await Client.connect( "localhost:7233", plugins=[ OpenAIAgentsPlugin( model_params=ModelActivityParameters( - start_to_close_timeout=timedelta(seconds=10) + start_to_close_timeout=timedelta(seconds=30) ) - ) - ] + ), + ], ) worker = Worker( @@ -119,12 +223,15 @@ if __name__ == "__main__": asyncio.run(worker_main()) ``` -We wrap the entire `worker_main` function body in the `set_open_ai_agent_temporal_overrides()` context manager. -This causes a Temporal activity to be invoked whenever the OpenAI Agents SDK invokes an LLM or calls a tool. -We also pass the `pydantic_data_converter` to the Temporal Client, which ensures proper serialization of pydantic models in OpenAI Agents SDK data. -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. +We use the `OpenAIAgentsPlugin` to configure Temporal for use with OpenAI Agents SDK. +The plugin automatically handles several important setup tasks: +- Ensures proper serialization of Pydantic types +- Propagates context for [OpenAI Agents tracing](https://openai.github.io/openai-agents-python/tracing/). +- Registers an activity for invoking model calls with the Temporal worker. +- Configures OpenAI Agents SDK to run model calls as Temporal activities. + -In order to launch the agent, use the standard Temporal workflow invocation: +### File 3: Client Execution (`run_hello_world_workflow.py`) ```python # File: run_hello_world_workflow.py @@ -141,7 +248,7 @@ async def main(): # Create client connected to server at the given address client = await Client.connect( "localhost:7233", - plugins=[OpenAIAgentsPlugin()] + plugins=[OpenAIAgentsPlugin()], ) # Execute a workflow @@ -158,14 +265,17 @@ if __name__ == "__main__": asyncio.run(main()) ``` -This launcher script executes the Temporal workflow to start the agent. +This file is a standard Temporal launch script. +We also configure the client with the `OpenAIAgentsPlugin` to ensure serialization is compatible with the worker. -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. +To run this example, see the detailed instructions in the [Temporal Python Samples Repository](https://github.com/temporalio/samples-python/tree/main/openai_agents). -## Using Temporal Activities as OpenAI Agents Tools +## Tool Calling -One of the powerful features of this integration is the ability to convert Temporal activities into OpenAI Agents tools using `activity_as_tool`. +### Temporal Activities as OpenAI Agents Tools + +One of the powerful features of this integration is the ability to convert Temporal activities into agent tools using `activity_as_tool`. This allows your agent to leverage Temporal's durable execution for tool calls. In the example below, we apply the `@activity.defn` decorator to the `get_weather` function to create a Temporal activity. @@ -207,48 +317,139 @@ class WeatherAgent: return result.final_output ``` +### Calling OpenAI Agents Tools inside Temporal Workflows -### Agent Handoffs - -The OpenAI Agents SDK supports agent handoffs, where one agent can transfer control to another agent. -In this example, one Temporal workflow wraps the entire multi-agent system: +For simple computations that don't involve external calls you can call the tool directly from the workflow by using the standard OpenAI Agents SDK `@functiontool` annotation. ```python -@workflow.defn -class CustomerServiceWorkflow: - def __init__(self): - self.current_agent = self.init_agents() - - def init_agents(self): - faq_agent = Agent( - name="FAQ Agent", - instructions="Answer frequently asked questions", - ) - - booking_agent = Agent( - name="Booking Agent", - instructions="Help with booking and seat changes", - ) - - triage_agent = Agent( - name="Triage Agent", - instructions="Route customers to the right agent", - handoffs=[faq_agent, booking_agent], - ) - - return triage_agent +from temporalio import workflow +from agents import Agent, Runner +from agents import function_tool +@function_tool +def calculate_circle_area(radius: float) -> float: + """Calculate the area of a circle given its radius.""" + import math + return math.pi * radius ** 2 + +@workflow.defn +class MathAssistantAgent: @workflow.run - async def run(self, customer_message: str) -> str: - result = await Runner.run( - starting_agent=self.current_agent, - input=customer_message, - context=self.context, + async def run(self, message: str) -> str: + agent = Agent( + name="Math Assistant", + instructions="You are a helpful math assistant. Use the available tools to help with calculations.", + tools=[calculate_circle_area], ) + result = await Runner.run(agent, input=message) return result.final_output ``` +Note that any tools that run in the workflow must respect the workflow execution restrictions, meaning no I/O or non-deterministic operations. +Of course, code running in the workflow can invoke a Temporal activity at any time. + +Tools that run in the workflow can also update OpenAI Agents context, which is read-only for tools run as Temporal activities. + + +## Feature Support + +This integration is presently subject to certain limitations. +Streaming and voice agents are not supported. +Certain tools are not suitable for a distributed computing environment, so these have been disabled as well. + +### Model Providers + +| Model Provider | Supported | +|:--------------|:---------:| +| OpenAI | Yes | +| LiteLLM | Yes | + +### Model Response format + +This integration does not presently support streaming. + +| Model Response | Supported | +|:--------------|:---------:| +| Get Response | Yes | +| Streaming | No | + +### Tools + +#### Tool Type + +`LocalShellTool` and `ComputerTool` are not suited to a distributed computing setting. + +| Tool Type | Supported | +|:-------------------|:---------:| +| FunctionTool | Yes | +| LocalShellTool | No | +| WebSearchTool | Yes | +| FileSearchTool | Yes | +| HostedMCPTool | Yes | +| ImageGenerationTool | Yes | +| CodeInterpreterTool | Yes | +| ComputerTool | No | + +#### Tool Context + +As described in [Tool Calling](#tool-calling), context propagation is read-only when Temporal activities are used as tools. + +| Context Propagation | Supported | +|:----------------------------------------|:---------:| +| Activity Tool receives copy of context | Yes | +| Activity Tool can update context | No | +| Function Tool received context | Yes | +| Function Tool can update context | Yes | + +### MCP + +Presently, MCP is supported only via `HostedMCPTool`, which uses the OpenAI Responses API and cloud MCP client behind it. +The OpenAI Agents SDK also supports MCP clients that run in application code, but this integration does not. + +| MCP Class | Supported | +|:-----------------------|:---------:| +| MCPServerStdio | No | +| MCPServerSse | No | +| MCPServerStreamableHttp| No | + +### Guardrails + +| Guardrail Type | Supported | +|:---------------|:---------:| +| Code | Yes | +| Agent | Yes | + +### Sessions + +SQLite storage is not suited to a distributed environment. + +| Feature | Supported | +|:---------------|:---------:| +| SQLiteSession | No | + +### Tracing + +| Tracing Provider | Supported | +|:-----------------|:---------:| +| OpenAI platform | Yes | + +### Voice + +| Mode | Supported | +|:------------------------|:---------:| +| Voice agents (pipelines)| No | +| Realtime agents | No | + +### Utilities + +The REPL utility is not suitable for a distributed setting. + +| Utility | Supported | +|:--------|:---------:| +| REPL | No | + ## Additional Examples You can find additional examples in the [Temporal Python Samples Repository](https://github.com/temporalio/samples-python/tree/main/openai_agents). +