chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
"""
|
||||
Blaxel-backed sandbox example for manual validation.
|
||||
|
||||
This example mirrors the other cloud extension runners. It supports:
|
||||
- Standard agent run (non-streaming and streaming).
|
||||
- PTY interactive session demo (agent-driven).
|
||||
- Blaxel Drive mount demo (persistent storage).
|
||||
|
||||
Prerequisites:
|
||||
uv sync --extra blaxel
|
||||
export OPENAI_API_KEY=...
|
||||
export BL_API_KEY=...
|
||||
export BL_WORKSPACE=...
|
||||
|
||||
Run:
|
||||
# Basic agent run
|
||||
uv run python examples/sandbox/extensions/blaxel_runner.py --stream
|
||||
|
||||
# With a specific image and region
|
||||
uv run python examples/sandbox/extensions/blaxel_runner.py \\
|
||||
--image blaxel/py-app --region us-pdx-1 --stream
|
||||
|
||||
# PTY terminal demo (agent-driven interactive Python session)
|
||||
uv run python examples/sandbox/extensions/blaxel_runner.py --demo pty
|
||||
|
||||
# Drive mount demo (requires an existing drive, defaults region to us-was-1)
|
||||
uv run python examples/sandbox/extensions/blaxel_runner.py \\
|
||||
--demo drive --drive-name my-drive
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from openai.types.responses import ResponseTextDeltaEvent
|
||||
|
||||
from agents import ModelSettings, Runner, set_tracing_disabled
|
||||
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.manifest import Environment
|
||||
|
||||
if __package__ is None or __package__ == "":
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
|
||||
|
||||
from examples.sandbox.misc.example_support import text_manifest, tool_call_name
|
||||
from examples.sandbox.misc.workspace_shell import WorkspaceShellCapability
|
||||
|
||||
try:
|
||||
from agents.extensions.sandbox import (
|
||||
DEFAULT_BLAXEL_WORKSPACE_ROOT,
|
||||
BlaxelDriveMountStrategy,
|
||||
BlaxelSandboxClient,
|
||||
BlaxelSandboxClientOptions,
|
||||
)
|
||||
from agents.extensions.sandbox.blaxel import BlaxelDriveMount
|
||||
except Exception as exc:
|
||||
raise SystemExit(
|
||||
"Blaxel sandbox examples require the optional repo extra.\n"
|
||||
"Install it with: uv sync --extra blaxel"
|
||||
) from exc
|
||||
|
||||
|
||||
DEFAULT_MODEL = "gpt-5.6-sol"
|
||||
DEFAULT_QUESTION = "Summarize this cloud sandbox workspace in 2 sentences."
|
||||
DEFAULT_PTY_QUESTION = (
|
||||
"Start an interactive Python session with `tty=true`. In that same session, compute "
|
||||
"`5 + 5`, then add 5 more to the previous result. Briefly report the outputs and "
|
||||
"confirm that you stayed in one Python process."
|
||||
)
|
||||
|
||||
|
||||
def _build_manifest() -> Manifest:
|
||||
"""Build a small demo manifest for the default agent run."""
|
||||
manifest = text_manifest(
|
||||
{
|
||||
"README.md": (
|
||||
"# Blaxel Demo Workspace\n\nThis workspace validates the Blaxel sandbox backend.\n"
|
||||
),
|
||||
"project/status.md": (
|
||||
"# Project Status\n\n"
|
||||
"- Backend: Blaxel cloud sandbox\n"
|
||||
"- Region: auto-selected\n"
|
||||
"- Features: exec, file I/O, PTY, drives, preview URLs\n"
|
||||
),
|
||||
"project/tasks.md": (
|
||||
"# Tasks\n\n"
|
||||
"1. Inspect the workspace files.\n"
|
||||
"2. List all features mentioned in status.md.\n"
|
||||
"3. Summarize in 2-3 sentences.\n"
|
||||
),
|
||||
}
|
||||
)
|
||||
return Manifest(
|
||||
root=DEFAULT_BLAXEL_WORKSPACE_ROOT,
|
||||
entries=manifest.entries,
|
||||
environment=Environment(
|
||||
value={"DEMO_ENV": "blaxel-agent-demo"},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _require_env(name: str) -> str:
|
||||
value = os.environ.get(name)
|
||||
if value:
|
||||
return value
|
||||
raise SystemExit(f"{name} must be set before running this example.")
|
||||
|
||||
|
||||
def _stream_event_banner(event_name: str, raw_item: object) -> str | None:
|
||||
_ = raw_item
|
||||
if event_name == "tool_called":
|
||||
return "[tool call]"
|
||||
if event_name == "tool_output":
|
||||
return "[tool output]"
|
||||
return None
|
||||
|
||||
|
||||
def _raw_item_call_id(raw_item: object) -> str | None:
|
||||
if isinstance(raw_item, dict):
|
||||
call_id = raw_item.get("call_id") or raw_item.get("id")
|
||||
else:
|
||||
call_id = getattr(raw_item, "call_id", None) or getattr(raw_item, "id", None)
|
||||
return call_id if isinstance(call_id, str) and call_id else None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PTY demo (agent-driven)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def _run_pty_demo(
|
||||
*,
|
||||
model: str,
|
||||
question: str,
|
||||
image: str | None,
|
||||
region: str | None,
|
||||
) -> None:
|
||||
"""Demonstrate PTY interaction: start an interactive Python process and continue it."""
|
||||
agent = SandboxAgent(
|
||||
name="Blaxel PTY Demo",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Complete the task by interacting with the sandbox through the shell capability. "
|
||||
"Keep the final answer concise. "
|
||||
"Preserve process state when the task depends on it. If you start an interactive "
|
||||
"program, continue using that same process instead of launching a second one."
|
||||
),
|
||||
default_manifest=Manifest(
|
||||
root=DEFAULT_BLAXEL_WORKSPACE_ROOT,
|
||||
entries=text_manifest(
|
||||
{
|
||||
"README.md": (
|
||||
"# Blaxel PTY Agent Example\n\n"
|
||||
"This workspace is used by the Blaxel PTY demo.\n"
|
||||
),
|
||||
}
|
||||
).entries,
|
||||
),
|
||||
capabilities=[Shell()],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
client = BlaxelSandboxClient()
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=client,
|
||||
options=BlaxelSandboxClientOptions(
|
||||
name=f"blaxel-demo-pty-{uuid.uuid4().hex[:8]}",
|
||||
image=image,
|
||||
region=region,
|
||||
),
|
||||
),
|
||||
workflow_name="Blaxel PTY sandbox example",
|
||||
)
|
||||
|
||||
try:
|
||||
result = Runner.run_streamed(agent, question, run_config=run_config)
|
||||
|
||||
saw_text_delta = False
|
||||
saw_any_text = False
|
||||
tool_names_by_call_id: dict[str, str] = {}
|
||||
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event" and isinstance(
|
||||
event.data, ResponseTextDeltaEvent
|
||||
):
|
||||
if not saw_text_delta:
|
||||
print("assistant> ", end="", flush=True)
|
||||
saw_text_delta = True
|
||||
print(event.data.delta, end="", flush=True)
|
||||
saw_any_text = True
|
||||
continue
|
||||
|
||||
if event.type != "run_item_stream_event":
|
||||
continue
|
||||
|
||||
raw_item = event.item.raw_item
|
||||
banner = _stream_event_banner(event.name, raw_item)
|
||||
if banner is None:
|
||||
continue
|
||||
|
||||
if saw_text_delta:
|
||||
print()
|
||||
saw_text_delta = False
|
||||
|
||||
if event.name == "tool_called":
|
||||
t_name = tool_call_name(raw_item)
|
||||
call_id = _raw_item_call_id(raw_item)
|
||||
if call_id is not None and t_name:
|
||||
tool_names_by_call_id[call_id] = t_name
|
||||
if t_name:
|
||||
banner = f"{banner} {t_name}"
|
||||
elif event.name == "tool_output":
|
||||
call_id = _raw_item_call_id(raw_item)
|
||||
output_tool_name = tool_names_by_call_id.get(call_id or "")
|
||||
if output_tool_name:
|
||||
banner = f"{banner} {output_tool_name}"
|
||||
|
||||
print(banner)
|
||||
|
||||
if saw_text_delta:
|
||||
print()
|
||||
if not saw_any_text:
|
||||
print(result.final_output)
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Drive demo
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def _run_drive_demo(
|
||||
*,
|
||||
model: str,
|
||||
question: str | None,
|
||||
image: str | None,
|
||||
region: str | None,
|
||||
drive_name: str | None,
|
||||
stream: bool,
|
||||
) -> None:
|
||||
"""Mount a Blaxel Drive and write a file to it."""
|
||||
if not drive_name:
|
||||
print("Usage: --demo drive --drive-name <name>")
|
||||
print()
|
||||
print("You need an existing Blaxel Drive. Create one at:")
|
||||
print(" https://app.blaxel.ai or via the Blaxel CLI.")
|
||||
return
|
||||
|
||||
# Blaxel drives must be in the same region as the sandbox.
|
||||
effective_region = region or os.environ.get("BL_REGION") or "us-was-1"
|
||||
mount_path = "/mnt/demo-drive"
|
||||
|
||||
manifest = Manifest(
|
||||
root=DEFAULT_BLAXEL_WORKSPACE_ROOT,
|
||||
entries={
|
||||
"README.md": File(
|
||||
content=(b"# Blaxel Drive Demo\n\nThe drive is mounted at /mnt/demo-drive.\n")
|
||||
),
|
||||
"drive": BlaxelDriveMount(
|
||||
drive_name=drive_name,
|
||||
drive_mount_path=mount_path,
|
||||
mount_strategy=BlaxelDriveMountStrategy(),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
marker = f"demo-{uuid.uuid4().hex[:8]}"
|
||||
agent = SandboxAgent(
|
||||
name="Blaxel Drive Demo",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Execute the exact shell commands the user gives you. "
|
||||
"Do not explore, do not run any other commands. "
|
||||
"Report the stdout and stderr of each command you ran. "
|
||||
"You must run the exact commands from the user message using the shell tool. "
|
||||
"Do not substitute, rewrite, or add any commands. Just execute and report output."
|
||||
),
|
||||
default_manifest=manifest,
|
||||
capabilities=[Shell()],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
client = BlaxelSandboxClient()
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=client,
|
||||
options=BlaxelSandboxClientOptions(
|
||||
name=f"blaxel-demo-drive-{uuid.uuid4().hex[:8]}",
|
||||
image=image,
|
||||
region=effective_region,
|
||||
),
|
||||
),
|
||||
workflow_name="Blaxel drive demo",
|
||||
)
|
||||
|
||||
effective_question = question or (
|
||||
f"Run: echo 'drive persistence ok ({marker})' > {mount_path}/{marker}.txt && "
|
||||
f"cat {mount_path}/{marker}.txt && ls {mount_path}"
|
||||
)
|
||||
|
||||
if not stream:
|
||||
result = await Runner.run(agent, effective_question, run_config=run_config)
|
||||
print(result.final_output)
|
||||
else:
|
||||
stream_result = Runner.run_streamed(agent, effective_question, run_config=run_config)
|
||||
saw_text_delta = False
|
||||
async for event in stream_result.stream_events():
|
||||
if event.type == "raw_response_event" and isinstance(
|
||||
event.data, ResponseTextDeltaEvent
|
||||
):
|
||||
if not saw_text_delta:
|
||||
print("assistant> ", end="", flush=True)
|
||||
saw_text_delta = True
|
||||
print(event.data.delta, end="", flush=True)
|
||||
if saw_text_delta:
|
||||
print()
|
||||
|
||||
await client.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Standard agent run (streaming / non-streaming)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def main(
|
||||
*,
|
||||
model: str,
|
||||
question: str | None,
|
||||
image: str | None,
|
||||
region: str | None,
|
||||
memory: int | None,
|
||||
ttl: str | None,
|
||||
pause_on_exit: bool,
|
||||
stream: bool,
|
||||
demo: str | None,
|
||||
drive_name: str | None,
|
||||
) -> None:
|
||||
_require_env("OPENAI_API_KEY")
|
||||
|
||||
# Handle dedicated demos.
|
||||
if demo == "pty":
|
||||
await _run_pty_demo(
|
||||
model=model,
|
||||
question=question or DEFAULT_PTY_QUESTION,
|
||||
image=image,
|
||||
region=region,
|
||||
)
|
||||
return
|
||||
|
||||
if demo == "drive":
|
||||
await _run_drive_demo(
|
||||
model=model,
|
||||
question=question,
|
||||
image=image,
|
||||
region=region,
|
||||
drive_name=drive_name,
|
||||
stream=stream,
|
||||
)
|
||||
return
|
||||
|
||||
manifest = _build_manifest()
|
||||
agent = SandboxAgent(
|
||||
name="Blaxel Sandbox Assistant",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Answer questions about the sandbox workspace. Inspect the files before answering "
|
||||
"and keep the response concise. "
|
||||
"Do not invent files or statuses that are not present in the workspace. Cite the "
|
||||
"file names you inspected. Also run `echo $DEMO_ENV` to confirm environment "
|
||||
"variables are set."
|
||||
),
|
||||
default_manifest=manifest,
|
||||
capabilities=[WorkspaceShellCapability()],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=BlaxelSandboxClient(),
|
||||
options=BlaxelSandboxClientOptions(
|
||||
name=f"blaxel-demo-agent-{uuid.uuid4().hex[:8]}",
|
||||
image=image,
|
||||
region=region,
|
||||
memory=memory,
|
||||
ttl=ttl,
|
||||
labels={"purpose": "agent-demo", "source": "blaxel-runner"},
|
||||
pause_on_exit=pause_on_exit,
|
||||
),
|
||||
),
|
||||
workflow_name="Blaxel sandbox example",
|
||||
)
|
||||
|
||||
effective_question = question or DEFAULT_QUESTION
|
||||
|
||||
if not stream:
|
||||
result = await Runner.run(agent, effective_question, run_config=run_config)
|
||||
print(result.final_output)
|
||||
return
|
||||
|
||||
stream_result = Runner.run_streamed(agent, effective_question, run_config=run_config)
|
||||
saw_text_delta = False
|
||||
async for event in stream_result.stream_events():
|
||||
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
|
||||
if not saw_text_delta:
|
||||
print("assistant> ", end="", flush=True)
|
||||
saw_text_delta = True
|
||||
print(event.data.delta, end="", flush=True)
|
||||
|
||||
if saw_text_delta:
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
set_tracing_disabled(True)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Blaxel sandbox demo -- showcases sandbox features.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=(
|
||||
"demos:\n"
|
||||
" agent Run a sandboxed agent (default)\n"
|
||||
" pty Agent-driven PTY interactive terminal\n"
|
||||
" drive Mount a Blaxel Drive (requires --drive-name)\n"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--demo",
|
||||
choices=["agent", "pty", "drive"],
|
||||
default="agent",
|
||||
help="Which demo to run (default: agent).",
|
||||
)
|
||||
parser.add_argument("--model", default=DEFAULT_MODEL, help="Model name.")
|
||||
parser.add_argument("--question", default=None, help="Override the default prompt.")
|
||||
parser.add_argument("--stream", action="store_true", help="Stream response.")
|
||||
parser.add_argument("--image", default=None, help="Sandbox image.")
|
||||
parser.add_argument("--region", default=None, help="Sandbox region.")
|
||||
parser.add_argument("--memory", type=int, default=None, help="Memory in MB.")
|
||||
parser.add_argument("--ttl", default=None, help="Sandbox TTL (e.g. '1h').")
|
||||
parser.add_argument("--pause-on-exit", action="store_true", help="Pause on exit.")
|
||||
parser.add_argument("--drive-name", default=None, help="Drive name for drive demo.")
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(
|
||||
main(
|
||||
model=args.model,
|
||||
question=args.question,
|
||||
image=args.image,
|
||||
region=args.region,
|
||||
memory=args.memory,
|
||||
ttl=args.ttl,
|
||||
pause_on_exit=args.pause_on_exit,
|
||||
stream=args.stream,
|
||||
demo=args.demo,
|
||||
drive_name=args.drive_name,
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user