Files
openai--openai-agents-python/examples/sandbox/extensions/temporal/local_hello_workflow.py
T
2026-07-13 12:39:17 +08:00

151 lines
5.5 KiB
Python

"""Minimal local Temporal SandboxAgent workflow example.
This example is intentionally smaller than ``temporal_sandbox_agent.py``. It starts a local
Temporal test server through the Temporal Python SDK, runs a ``SandboxAgent`` workflow against
the local Unix sandbox backend, and then shuts everything down.
It does not require the Temporal CLI, a long-running Temporal server, or cloud sandbox backend
credentials. It does require ``OPENAI_API_KEY`` because the model call runs through the Temporal
OpenAI Agents plugin as an activity.
Usage:
uv run --extra temporal python -m examples.sandbox.extensions.temporal.local_hello_workflow
"""
from __future__ import annotations
import asyncio
import os
from datetime import timedelta
from temporalio import workflow
from temporalio.client import Client
from temporalio.contrib.openai_agents import (
ModelActivityParameters,
OpenAIAgentsPlugin,
SandboxClientProvider,
)
from temporalio.contrib.openai_agents.workflow import temporal_sandbox_client
from temporalio.testing import WorkflowEnvironment
from temporalio.worker import Worker
from temporalio.worker.workflow_sandbox import SandboxedWorkflowRunner, SandboxRestrictions
from agents import ModelSettings, Runner
from agents.run import RunConfig
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
from agents.sandbox.capabilities import Shell
from agents.sandbox.entries import File
from agents.sandbox.sandboxes import UnixLocalSandboxClient, UnixLocalSandboxClientOptions
TASK_QUEUE = "local-temporal-sandbox-agent"
WORKFLOW_ID = "local-temporal-sandbox-agent-workflow"
DEFAULT_MODEL = "gpt-5.4-mini"
EXPECTED_GREETING = "Temporal sandbox says hello from a local file"
TRACE_MODE_NONE = "none"
TRACE_MODE_OPENAI = "openai"
TRACE_MODE_OPENAI_WITH_TEMPORAL_SPANS = "openai_with_temporal_spans"
TRACE_MODES = {
TRACE_MODE_NONE,
TRACE_MODE_OPENAI,
TRACE_MODE_OPENAI_WITH_TEMPORAL_SPANS,
}
@workflow.defn
class LocalSandboxAgentWorkflow:
@workflow.run
async def run(self, model: str, trace_mode: str) -> str:
agent = SandboxAgent(
name="Local Temporal Sandbox Agent",
model=model,
instructions=(
"Inspect the sandbox workspace with the shell tool before answering. "
"Report the greeting from README.md exactly."
),
default_manifest=Manifest(
entries={
"README.md": File(content=b"Temporal sandbox says hello from a local file.\n"),
}
),
capabilities=[Shell()],
model_settings=ModelSettings(tool_choice="required"),
)
result = await Runner.run(
agent,
"Read README.md and report its greeting.",
run_config=RunConfig(
sandbox=SandboxRunConfig(
client=temporal_sandbox_client("local"),
options=UnixLocalSandboxClientOptions(),
),
workflow_name="Local Temporal SandboxAgent workflow",
tracing_disabled=trace_mode == TRACE_MODE_NONE,
),
)
return str(result.final_output)
def _client_with_plugin(client: Client, trace_mode: str) -> Client:
plugin = OpenAIAgentsPlugin(
model_params=ModelActivityParameters(start_to_close_timeout=timedelta(seconds=120)),
sandbox_clients=[SandboxClientProvider("local", UnixLocalSandboxClient())],
add_temporal_spans=trace_mode == TRACE_MODE_OPENAI_WITH_TEMPORAL_SPANS,
)
config = client.config()
config["plugins"] = [*config.get("plugins", []), plugin]
return Client(**config)
def _require_env(name: str) -> None:
if not os.environ.get(name):
raise SystemExit(f"{name} must be set before running this example.")
def _trace_mode_from_env() -> str:
trace_mode = os.getenv("EXAMPLES_TEMPORAL_TRACE", TRACE_MODE_OPENAI).strip().lower()
if trace_mode not in TRACE_MODES:
supported = ", ".join(sorted(TRACE_MODES))
raise SystemExit(
f"EXAMPLES_TEMPORAL_TRACE must be one of: {supported}. Got {trace_mode!r}."
)
return trace_mode
async def main() -> None:
_require_env("OPENAI_API_KEY")
model = os.getenv("EXAMPLES_TEMPORAL_MODEL", DEFAULT_MODEL)
trace_mode = _trace_mode_from_env()
print(f"Using model: {model}")
print(f"Using trace mode: {trace_mode}")
print("Starting local Temporal test server...")
async with await WorkflowEnvironment.start_time_skipping() as env:
client = _client_with_plugin(env.client, trace_mode)
print("Starting local Temporal worker...")
async with Worker(
client,
task_queue=TASK_QUEUE,
workflows=[LocalSandboxAgentWorkflow],
workflow_runner=SandboxedWorkflowRunner(
restrictions=SandboxRestrictions.default.with_passthrough_modules(
"annotated_types",
"pydantic_core",
),
),
):
result = await client.execute_workflow(
LocalSandboxAgentWorkflow.run,
args=[model, trace_mode],
id=WORKFLOW_ID,
task_queue=TASK_QUEUE,
)
print(f"Workflow result: {result}")
if EXPECTED_GREETING not in result:
raise RuntimeError(f"Expected workflow result to contain {EXPECTED_GREETING!r}.")
print("Local Temporal SandboxAgent workflow completed successfully.")
if __name__ == "__main__":
asyncio.run(main())