Skip to content

OpenAI Agents agent pattern examples #224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 29, 2025
91 changes: 60 additions & 31 deletions openai_agents/agent_patterns/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left two comments at #226 that apply here as well

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Common agentic patterns extended with Temporal's durable execution capabilities.

*Adapted from [OpenAI Agents SDK agent patterns](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns)*

Before running these examples, be sure to review the [prerequisites and background on the integration](../README.md).

## Running the Examples

First, start the worker (supports all patterns):
Expand All @@ -13,56 +15,83 @@ uv run openai_agents/agent_patterns/run_worker.py

Then run individual examples in separate terminals:

## Deterministic Flows

**TODO**

A common tactic is to break down a task into a series of smaller steps. Each task can be performed by an agent, and the output of one agent is used as input to the next. For example, if your task was to generate a story, you could break it down into the following steps:

1. Generate an outline
2. Generate the story
3. Generate the ending

Each of these steps can be performed by an agent. The output of one agent is used as input to the next.

## Handoffs and Routing

**TODO**

In many situations, you have specialized sub-agents that handle specific tasks. You can use handoffs to route the task to the right agent.
### Deterministic Flows
Sequential agent execution with validation gates - demonstrates breaking complex tasks into smaller steps:
```bash
uv run openai_agents/agent_patterns/run_deterministic_workflow.py
```

For example, you might have a frontline agent that receives a request, and then hands off to a specialized agent based on the language of the request.
### Parallelization
Run multiple agents in parallel and select the best result - useful for improving quality or reducing latency:
```bash
uv run openai_agents/agent_patterns/run_parallelization_workflow.py
```

## Agents as Tools
### LLM-as-a-Judge
Iterative improvement using feedback loops - generate content, evaluate it, and improve until satisfied:
```bash
uv run openai_agents/agent_patterns/run_llm_as_a_judge_workflow.py
```

The mental model for handoffs is that the new agent "takes over". It sees the previous conversation history, and owns the conversation from that point onwards. However, this is not the only way to use agents. You can also use agents as a tool - the tool agent goes off and runs on its own, and then returns the result to the original agent.
### Agents as Tools
Use agents as callable tools within other agents - enables composition and specialized task delegation:
```bash
uv run openai_agents/agent_patterns/run_agents_as_tools_workflow.py
```

For example, you could model a translation task as tool calls instead: rather than handing over to the language-specific agent, you could call the agent as a tool, and then use the result in the next step. This enables things like translating multiple languages at once.
### Agent Routing and Handoffs
Route requests to specialized agents based on content analysis (adapted for non-streaming):
```bash
uv run openai_agents/agent_patterns/run_routing_workflow.py
```

### Input Guardrails
Pre-execution validation to prevent unwanted requests - demonstrates safety mechanisms:
```bash
uv run openai_agents/agent_patterns/run_agents_as_tools_workflow.py
uv run openai_agents/agent_patterns/run_input_guardrails_workflow.py
```

## LLM-as-a-Judge
### Output Guardrails
Post-execution validation to detect sensitive content - ensures safe responses:
```bash
uv run openai_agents/agent_patterns/run_output_guardrails_workflow.py
```

**TODO**
### Forcing Tool Use
Control tool execution strategies - choose between different approaches to tool usage:
```bash
uv run openai_agents/agent_patterns/run_forcing_tool_use_workflow.py
```

LLMs can often improve the quality of their output if given feedback. A common pattern is to generate a response using a model, and then use a second model to provide feedback. You can even use a small model for the initial generation and a larger model for the feedback, to optimize cost.
## Pattern Details

For example, you could use an LLM to generate an outline for a story, and then use a second LLM to evaluate the outline and provide feedback. You can then use the feedback to improve the outline, and repeat until the LLM is satisfied with the outline.
### Deterministic Flows
A common tactic is to break down a task into a series of smaller steps. Each task can be performed by an agent, and the output of one agent is used as input to the next. For example, if your task was to generate a story, you could break it down into the following steps:

## Parallelization
1. Generate an outline
2. Check outline quality and genre
3. Write the story (only if outline passes validation)

**TODO**
Each of these steps can be performed by an agent. The output of one agent is used as input to the next.

### Parallelization
Running multiple agents in parallel is a common pattern. This can be useful for both latency (e.g. if you have multiple steps that don't depend on each other) and also for other reasons e.g. generating multiple responses and picking the best one.

## Guardrails
### LLM-as-a-Judge
LLMs can often improve the quality of their output if given feedback. A common pattern is to generate a response using a model, and then use a second model to provide feedback. You can even use a small model for the initial generation and a larger model for the feedback, to optimize cost.

**TODO**
### Agents as Tools
The mental model for handoffs is that the new agent "takes over". It sees the previous conversation history, and owns the conversation from that point onwards. However, this is not the only way to use agents. You can also use agents as a tool - the tool agent goes off and runs on its own, and then returns the result to the original agent.

### Guardrails
Related to parallelization, you often want to run input guardrails to make sure the inputs to your agents are valid. For example, if you have a customer support agent, you might want to make sure that the user isn't trying to ask for help with a math problem.

You can definitely do this without any special Agents SDK features by using parallelization, but we support a special guardrail primitive. Guardrails can have a "tripwire" - if the tripwire is triggered, the agent execution will immediately stop and a `GuardrailTripwireTriggered` exception will be raised.

This is really useful for latency: for example, you might have a very fast model that runs the guardrail and a slow model that runs the actual agent. You wouldn't want to wait for the slow model to finish, so guardrails let you quickly reject invalid inputs.
This is really useful for latency: for example, you might have a very fast model that runs the guardrail and a slow model that runs the actual agent. You wouldn't want to wait for the slow model to finish, so guardrails let you quickly reject invalid inputs.

## Omitted Examples

The following patterns from the [reference repository](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns) are not included in this Temporal adaptation:

- **Streaming Guardrails**: Requires streaming capabilities which are not yet available in the Temporal integration
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ async def main():
# Execute a workflow
result = await client.execute_workflow(
AgentsAsToolsWorkflow.run,
"Translate to English: '¿Cómo estás?'",
id="my-workflow-id",
task_queue="openai-agents-task-queue",
"Please translate 'Good morning, how are you?' to Spanish and French",
id="agents-as-tools-workflow-example",
task_queue="openai-agents-patterns-task-queue",
)

print(f"Result: {result}")
Expand Down
31 changes: 31 additions & 0 deletions openai_agents/agent_patterns/run_deterministic_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.deterministic_workflow import (
DeterministicWorkflow,
)


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute a workflow
result = await client.execute_workflow(
DeterministicWorkflow.run,
"Write a science fiction story about time travel",
id="deterministic-workflow-example",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
50 changes: 50 additions & 0 deletions openai_agents/agent_patterns/run_forcing_tool_use_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.forcing_tool_use_workflow import (
ForcingToolUseWorkflow,
)


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute workflows with different tool use behaviors
print("Testing default behavior:")
result1 = await client.execute_workflow(
ForcingToolUseWorkflow.run,
"default",
id="forcing-tool-use-workflow-default",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Default result: {result1}")

print("\nTesting first_tool behavior:")
result2 = await client.execute_workflow(
ForcingToolUseWorkflow.run,
"first_tool",
id="forcing-tool-use-workflow-first-tool",
task_queue="openai-agents-patterns-task-queue",
)
print(f"First tool result: {result2}")

print("\nTesting custom behavior:")
result3 = await client.execute_workflow(
ForcingToolUseWorkflow.run,
"custom",
id="forcing-tool-use-workflow-custom",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Custom result: {result3}")


if __name__ == "__main__":
asyncio.run(main())
40 changes: 40 additions & 0 deletions openai_agents/agent_patterns/run_input_guardrails_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.input_guardrails_workflow import (
InputGuardrailsWorkflow,
)


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute a workflow with a normal question (should pass)
result1 = await client.execute_workflow(
InputGuardrailsWorkflow.run,
"What's the capital of California?",
id="input-guardrails-workflow-normal",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Normal question result: {result1}")

# Execute a workflow with a math homework question (should be blocked)
result2 = await client.execute_workflow(
InputGuardrailsWorkflow.run,
"Can you help me solve for x: 2x + 5 = 11?",
id="input-guardrails-workflow-blocked",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Math homework result: {result2}")


if __name__ == "__main__":
asyncio.run(main())
31 changes: 31 additions & 0 deletions openai_agents/agent_patterns/run_llm_as_a_judge_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.llm_as_a_judge_workflow import (
LLMAsAJudgeWorkflow,
)


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute a workflow
result = await client.execute_workflow(
LLMAsAJudgeWorkflow.run,
"A thrilling adventure story about pirates searching for treasure",
id="llm-as-a-judge-workflow-example",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
40 changes: 40 additions & 0 deletions openai_agents/agent_patterns/run_output_guardrails_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.output_guardrails_workflow import (
OutputGuardrailsWorkflow,
)


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute a workflow with a normal question (should pass)
result1 = await client.execute_workflow(
OutputGuardrailsWorkflow.run,
"What's the capital of California?",
id="output-guardrails-workflow-normal",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Normal question result: {result1}")

# Execute a workflow with input that might trigger sensitive data output
result2 = await client.execute_workflow(
OutputGuardrailsWorkflow.run,
"My phone number is 650-123-4567. Where do you think I live?",
id="output-guardrails-workflow-sensitive",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Sensitive data result: {result2}")


if __name__ == "__main__":
asyncio.run(main())
31 changes: 31 additions & 0 deletions openai_agents/agent_patterns/run_parallelization_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.parallelization_workflow import (
ParallelizationWorkflow,
)


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute a workflow
result = await client.execute_workflow(
ParallelizationWorkflow.run,
"Hello, world! How are you today?",
id="parallelization-workflow-example",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
29 changes: 29 additions & 0 deletions openai_agents/agent_patterns/run_routing_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import asyncio

from temporalio.client import Client
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin

from openai_agents.agent_patterns.workflows.routing_workflow import RoutingWorkflow


async def main():
# Create client connected to server at the given address
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(),
],
)

# Execute a workflow
result = await client.execute_workflow(
RoutingWorkflow.run,
"Bonjour! Comment allez-vous aujourd'hui?",
id="routing-workflow-example",
task_queue="openai-agents-patterns-task-queue",
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
Loading
Loading