Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openai_agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ Each directory contains a complete example with its own README for detailed inst
- **[Customer Service](./customer_service/README.md)** - Interactive customer service agent with escalation capabilities, demonstrating conversational workflows.
- **[Reasoning Content](./reasoning_content/README.md)** - Example of how to retrieve the thought process of reasoning models.
- **[Financial Research Agent](./financial_research_agent/README.md)** - Multi-agent financial research system with planner, search, analyst, writer, and verifier agents collaborating.
- **[Sandbox](./sandbox/README.md)** - `SandboxAgent` with a shell and filesystem, where every sandbox operation runs as a Temporal activity. **Pre-release.**
- **[Streaming](./streaming/README.md)** - `Runner.run_streamed` with buffered token streaming to external subscribers via `temporalio.contrib.workflow_streams`. **Experimental.**
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""


# @@@SNIPSTART python-openai-agents-agent-as-tool-workflow
def orchestrator_agent() -> Agent:
spanish_agent = Agent(
name="spanish_agent",
Expand Down Expand Up @@ -52,6 +53,9 @@ def orchestrator_agent() -> Agent:
return orchestrator_agent


# @@@SNIPEND


def synthesizer_agent() -> Agent:
return Agent(
name="synthesizer_agent",
Expand Down
4 changes: 4 additions & 0 deletions openai_agents/basic/activities/get_weather_activity.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# @@@SNIPSTART python-openai-agents-weather-activity
from dataclasses import dataclass

from temporalio import activity
Expand All @@ -16,3 +17,6 @@ async def get_weather(city: str) -> Weather:
Get the weather for a given city.
"""
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")


# @@@SNIPEND
2 changes: 2 additions & 0 deletions openai_agents/basic/run_hello_world_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

async def main():
# Create client connected to server at the given address
# @@@SNIPSTART python-openai-agents-hello-world-client
client = await Client.connect(
"localhost:7233",
plugins=[
Expand All @@ -23,6 +24,7 @@ async def main():
task_queue="openai-agents-basic-task-queue",
)
print(f"Result: {result}")
# @@@SNIPEND


if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions openai_agents/basic/run_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

async def main():
# Create client connected to server at the given address
# @@@SNIPSTART python-openai-agents-hello-world-worker
client = await Client.connect(
"localhost:7233",
plugins=[
Expand All @@ -44,6 +45,7 @@ async def main():
),
],
)
# @@@SNIPEND

worker = Worker(
client,
Expand Down
4 changes: 4 additions & 0 deletions openai_agents/basic/workflows/hello_world_workflow.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# @@@SNIPSTART python-openai-agents-hello-world-workflow
from agents import Agent, Runner
from temporalio import workflow

Expand All @@ -13,3 +14,6 @@ async def run(self, prompt: str) -> str:

result = await Runner.run(agent, input=prompt)
return result.final_output


# @@@SNIPEND
4 changes: 4 additions & 0 deletions openai_agents/basic/workflows/tools_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from openai_agents.basic.activities.get_weather_activity import get_weather


# @@@SNIPSTART python-openai-agents-activity-tool-workflow
@workflow.defn
class ToolsWorkflow:
@workflow.run
Expand All @@ -25,3 +26,6 @@ async def run(self, question: str) -> str:

result = await Runner.run(agent, input=question)
return result.final_output


# @@@SNIPEND
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def __init__(
customer_service_state.input_items if customer_service_state else []
)

# @@@SNIPSTART python-openai-agents-continue-as-new-workflow
@workflow.run
async def run(
self, customer_service_state: CustomerServiceWorkflowState | None = None
Expand All @@ -73,6 +74,8 @@ async def run(
)
)

# @@@SNIPEND

@workflow.query
def get_chat_history(self) -> list[str]:
return self.printed_history
Expand Down
4 changes: 4 additions & 0 deletions openai_agents/hosted_mcp/workflows/approval_mcp_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from temporalio import workflow


# @@@SNIPSTART python-openai-agents-hosted-mcp-approval-workflow
def approval_callback(request: MCPToolApprovalRequest) -> MCPToolApprovalFunctionResult:
"""Simple approval callback that logs the request and approves by default.

Expand All @@ -23,6 +24,9 @@ def approval_callback(request: MCPToolApprovalRequest) -> MCPToolApprovalFunctio
return result


# @@@SNIPEND


@workflow.defn
class ApprovalMCPWorkflow:
@workflow.run
Expand Down
4 changes: 4 additions & 0 deletions openai_agents/hosted_mcp/workflows/simple_mcp_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from temporalio import workflow


# @@@SNIPSTART python-openai-agents-hosted-mcp-workflow
@workflow.defn
class SimpleMCPWorkflow:
@workflow.run
Expand All @@ -26,3 +27,6 @@ async def run(

result = await Runner.run(agent, question)
return result.final_output


# @@@SNIPEND
2 changes: 2 additions & 0 deletions openai_agents/mcp/run_file_system_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ async def main():
current_dir = os.path.dirname(os.path.abspath(__file__))
samples_dir = os.path.join(current_dir, "sample_files")

# @@@SNIPSTART python-openai-agents-stateless-mcp-worker
file_system_server = StatelessMCPServerProvider(
"FileSystemServer",
lambda: MCPServerStdio(
Expand All @@ -48,6 +49,7 @@ async def main():
),
],
)
# @@@SNIPEND

worker = Worker(
client,
Expand Down
2 changes: 2 additions & 0 deletions openai_agents/mcp/run_memory_research_scratchpad_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
async def main():
logging.basicConfig(level=logging.INFO)

# @@@SNIPSTART python-openai-agents-stateful-mcp-worker
memory_server_provider = StatefulMCPServerProvider(
"MemoryServer",
lambda _: MCPServerStdio(
Expand All @@ -47,6 +48,7 @@ async def main():
),
],
)
# @@@SNIPEND

worker = Worker(
client,
Expand Down
2 changes: 2 additions & 0 deletions openai_agents/mcp/workflows/file_system_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class FileSystemWorkflow:
@workflow.run
async def run(self) -> str:
with trace(workflow_name="MCP File System Example"):
# @@@SNIPSTART python-openai-agents-stateless-mcp-workflow
server: MCPServer = openai_agents.workflow.stateless_mcp_server(
"FileSystemServer"
)
Expand All @@ -19,6 +20,7 @@ async def run(self) -> str:
instructions="Use the tools to read the filesystem and answer questions based on those files.",
mcp_servers=[server],
)
# @@@SNIPEND

# List the files it can read
message = "Read the files and list them."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
class MemoryResearchScratchpadWorkflow:
@workflow.run
async def run(self) -> str:
# @@@SNIPSTART python-openai-agents-stateful-mcp-workflow
async with temporal_openai_agents.workflow.stateful_mcp_server(
"MemoryServer",
) as server:
Expand All @@ -57,6 +58,7 @@ async def run(self) -> str:
mcp_servers=[server],
model_settings=ModelSettings(tool_choice="required"),
)
# @@@SNIPEND

# Step 1: Write seed notes to memory
write_prompt_lines = [
Expand Down
59 changes: 59 additions & 0 deletions openai_agents/sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Sandbox OpenAI Agents

> **Pre-release.** Sandbox support in `temporalio.contrib.openai_agents` is
> subject to change before general availability.

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

`SandboxAgent` from the OpenAI Agents SDK gives an agent a machine to work on:
a shell it can run commands in and a filesystem it can read and write. The
plugin runs every one of those operations as a Temporal activity against a
`SandboxClientProvider` registered on the worker, so sandbox work is
observable, retryable, and recoverable like any other activity. The sandbox
session state is serialized with the workflow, so a worker restart part-way
through a run resumes against the same session.

The workflow refers to a backend by name. `temporal_sandbox_client("local")`
resolves to whichever `SandboxClientProvider` the worker registered under
`"local"`, and the name becomes the prefix of that backend's activity names —
which is what lets several backends coexist on one worker. Names must match
exactly.

This sample uses `UnixLocalSandboxClient`, which runs commands on the worker
host and needs no credentials beyond `OPENAI_API_KEY`. **The agent gets a real
shell on the machine running the worker**, so treat it accordingly: for
anything you would not run locally, register a remote client such as
`DaytonaSandboxClient` or `E2BSandboxClient` from
`agents.extensions.sandbox` instead. Only the worker changes — the workflow
still just names a provider.

## Running the Example

First, start the worker:

```bash
uv run openai_agents/sandbox/run_worker.py
```

Then, in another terminal, run the workflow:

```bash
uv run openai_agents/sandbox/run_local_sandbox_workflow.py
```

The agent writes a file in the sandbox, reads it back, and reports what it
found. In the Web UI at http://localhost:8233 the run shows the model
activities interleaved with the `local-sandbox_session_*` activities that carry
out the sandbox work.

## Notes

* A default `SandboxAgent` already carries the `Filesystem`, `Shell`, and
`Compaction` capabilities, so this sample declares no tools of its own.
* `temporal_sandbox_client()` takes an optional `ActivityConfig` for timeouts
and retries on the sandbox activities. It defaults to a 5-minute
`start_to_close_timeout`.
* A single workflow can target several backends by calling
`temporal_sandbox_client()` once per name, as long as the worker registers a
provider for each.
31 changes: 31 additions & 0 deletions openai_agents/sandbox/run_local_sandbox_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

import asyncio

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

from openai_agents.sandbox.shared import TASK_QUEUE
from openai_agents.sandbox.workflows.local_sandbox_workflow import (
LocalSandboxWorkflow,
)


async def main() -> None:
client = await Client.connect(
"localhost:7233",
plugins=[OpenAIAgentsPlugin()],
)

result = await client.execute_workflow(
LocalSandboxWorkflow.run,
"Write a file holding the first 20 Fibonacci numbers, one per line, "
"then tell me how many lines it has and what the last one is.",
id="openai-agents-sandbox",
task_queue=TASK_QUEUE,
)
print(f"Result: {result}")


if __name__ == "__main__":
asyncio.run(main())
51 changes: 51 additions & 0 deletions openai_agents/sandbox/run_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from __future__ import annotations

import asyncio
from datetime import timedelta

from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
from temporalio.client import Client
from temporalio.contrib.openai_agents import (
ModelActivityParameters,
OpenAIAgentsPlugin,
SandboxClientProvider,
)
from temporalio.worker import Worker

from openai_agents.sandbox.shared import SANDBOX_PROVIDER, TASK_QUEUE
from openai_agents.sandbox.workflows.local_sandbox_workflow import (
LocalSandboxWorkflow,
)


async def main() -> None:
# @@@SNIPSTART python-openai-agents-sandbox-worker
client = await Client.connect(
"localhost:7233",
plugins=[
OpenAIAgentsPlugin(
model_params=ModelActivityParameters(
start_to_close_timeout=timedelta(seconds=60)
),
# The plugin registers one set of sandbox activities per
# provider, prefixed with the provider name. Register several
# providers to let one worker serve several backends.
sandbox_clients=[
SandboxClientProvider(SANDBOX_PROVIDER, UnixLocalSandboxClient()),
],
),
],
)
# @@@SNIPEND

worker = Worker(
client,
task_queue=TASK_QUEUE,
workflows=[LocalSandboxWorkflow],
)
print("Worker started. Ctrl+C to exit.")
await worker.run()


if __name__ == "__main__":
asyncio.run(main())
9 changes: 9 additions & 0 deletions openai_agents/sandbox/shared.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

TASK_QUEUE = "openai-agents-sandbox-task-queue"

# Name the worker registers its SandboxClientProvider under, and the name the
# workflow passes to temporal_sandbox_client(). The two must match exactly:
# the name becomes the prefix of that backend's activity names, which is what
# lets several backends share one worker.
SANDBOX_PROVIDER = "local"
Loading
Loading