chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user