Skip to content
Open
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
43 changes: 43 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Environments
.env**
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Git
.git
.gitignore

# Misc
.DS_Store
50 changes: 50 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# syntax=docker/dockerfile:1.3
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/

# Install system dependencies
RUN apt-get update && apt-get install -y \
htop \
vim \
curl \
tar \
python3-dev \
postgresql-client \
build-essential \
libpq-dev \
gcc \
cmake \
netcat-openbsd \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN uv pip install --system --upgrade pip setuptools wheel

ENV UV_HTTP_TIMEOUT=1000

# Copy pyproject.toml and README.md to install dependencies
COPY 00_sync/060_harness_openai/pyproject.toml /app/060_harness_openai/pyproject.toml
COPY 00_sync/060_harness_openai/README.md /app/060_harness_openai/README.md

WORKDIR /app/060_harness_openai

# Copy the project code
COPY 00_sync/060_harness_openai/project /app/060_harness_openai/project

# Copy the test files
COPY 00_sync/060_harness_openai/tests /app/060_harness_openai/tests

# Copy shared test utilities
COPY test_utils /app/test_utils

# Install the required Python packages with dev dependencies
RUN uv pip install --system .[dev]

# Set environment variables
ENV PYTHONPATH=/app

# Set test environment variables
ENV AGENT_NAME=s060-harness-openai

# Run the agent using uvicorn
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
35 changes: 35 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Sync OpenAI Agents on the unified harness surface

A sync (HTTP) Agentex agent that runs the OpenAI Agents SDK and delivers its
output through the **unified harness surface**.

## What this demonstrates

The OpenAI Agents SDK produces native streaming events. This tutorial wraps a
`Runner.run_streamed` result in an `OpenAITurn` — the provider -> canonical
`StreamTaskMessage*` adapter — and forwards the canonical stream to the frontend
via `UnifiedEmitter.yield_turn`. The same `OpenAITurn` flows unchanged through
`auto_send_turn` in the async (`130_harness_openai`) and temporal
(`140_harness_openai`) variants; only the delivery method differs.

```python
result = Runner.run_streamed(starting_agent=agent, input=user_message)
turn = OpenAITurn(result=result, model="gpt-4o")
emitter = UnifiedEmitter(task_id=task_id, trace_id=task_id, parent_span_id=parent_span_id)
async for event in emitter.yield_turn(turn):
yield event
```

## Run it

```bash
agentex agents run --manifest manifest.yaml
```

## Test it

The offline test exercises the harness wiring without a server or API key:

```bash
pytest tests/test_agent.py -v
```
58 changes: 58 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
build:
context:
root: ../../
include_paths:
- 00_sync/060_harness_openai
- test_utils
dockerfile: 00_sync/060_harness_openai/Dockerfile
dockerignore: 00_sync/060_harness_openai/.dockerignore

local_development:
agent:
port: 8000
host_address: host.docker.internal
paths:
acp: project/acp.py

agent:
acp_type: sync
name: s060-harness-openai
description: A sync OpenAI Agents SDK agent on the unified harness surface

temporal:
enabled: false

credentials:
- env_var_name: OPENAI_API_KEY
secret_name: openai-api-key
secret_key: api-key
- env_var_name: REDIS_URL
secret_name: redis-url-secret
secret_key: url
- env_var_name: SGP_API_KEY
secret_name: sgp-api-key
secret_key: api-key
- env_var_name: SGP_ACCOUNT_ID
secret_name: sgp-account-id
secret_key: account-id
- env_var_name: SGP_CLIENT_BASE_URL
secret_name: sgp-client-base-url
secret_key: url

deployment:
image:
repository: ""
tag: "latest"

global:
agent:
name: "s060-harness-openai"
description: "A sync OpenAI Agents SDK agent on the unified harness surface"
replicaCount: 1
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1000m"
memory: "2Gi"
Empty file.
87 changes: 87 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/project/acp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""ACP handler for the sync OpenAI Agents harness tutorial.

This is the API layer. It runs the OpenAI Agents SDK via ``Runner.run_streamed``,
wraps the streamed run in an ``OpenAITurn`` (the provider -> canonical
``StreamTaskMessage*`` adapter), and forwards the canonical stream to the
Agentex frontend via ``UnifiedEmitter.yield_turn`` — the same harness surface
used by the async and temporal variants of this tutorial.
"""

from __future__ import annotations

import os
from typing import AsyncGenerator

from dotenv import load_dotenv

load_dotenv()

from agents import Runner

from agentex.lib import adk
from project.agent import MODEL_NAME, create_agent
from agentex.lib.types.acp import SendMessageParams
from agentex.lib.types.tracing import SGPTracingProcessorConfig
from agentex.lib.utils.logging import make_logger
from agentex.lib.sdk.fastacp.fastacp import FastACP
from agentex.lib.core.harness.emitter import UnifiedEmitter
from agentex.types.task_message_update import TaskMessageUpdate
from agentex.types.task_message_content import TaskMessageContent
from agentex.lib.adk.providers._modules.openai_turn import OpenAITurn
from agentex.lib.core.tracing.tracing_processor_manager import add_tracing_processor_config

logger = make_logger(__name__)

# LiteLLM proxy auth: copy LITELLM_API_KEY to OPENAI_API_KEY for OpenAI client
# compatibility, so the same example works behind the Scale LiteLLM gateway.
_litellm_key = os.environ.get("LITELLM_API_KEY")
if _litellm_key and not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = _litellm_key

add_tracing_processor_config(
SGPTracingProcessorConfig(
sgp_api_key=os.environ.get("SGP_API_KEY", ""),
sgp_account_id=os.environ.get("SGP_ACCOUNT_ID", ""),
sgp_base_url=os.environ.get("SGP_CLIENT_BASE_URL", ""),
)
)

acp = FastACP.create(acp_type="sync")

_agent = None


def get_agent():
"""Get or create the OpenAI Agents SDK agent instance."""
global _agent
if _agent is None:
_agent = create_agent()
return _agent


@acp.on_message_send
async def handle_message_send(
params: SendMessageParams,
) -> TaskMessageContent | list[TaskMessageContent] | AsyncGenerator[TaskMessageUpdate, None]:
"""Handle incoming messages, streaming tokens and tool calls via the harness."""
agent = get_agent()
task_id = params.task.id
user_message = params.content.content
logger.info(f"Processing message for task {task_id}")

async with adk.tracing.span(
trace_id=task_id,
task_id=task_id,
name="message",
input={"message": user_message},
data={"__span_type__": "AGENT_WORKFLOW"},
) as turn_span:
result = Runner.run_streamed(starting_agent=agent, input=user_message)
turn = OpenAITurn(result=result, model=MODEL_NAME)
emitter = UnifiedEmitter(
task_id=task_id,
trace_id=task_id,
parent_span_id=turn_span.id if turn_span else None,
)
async for event in emitter.yield_turn(turn):
yield event
47 changes: 47 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/project/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""OpenAI Agents SDK agent definition for the harness tutorial.

The agent is the boundary between this module and the API layer (acp.py).
The OpenAI Agents SDK runs its own tool-call loop internally; acp.py wraps a
``Runner.run_streamed`` result with ``OpenAITurn`` so it flows through the
unified harness surface.
"""

from __future__ import annotations

from datetime import datetime

from agents import Agent, function_tool, set_tracing_disabled

from project.tools import get_weather

# Disable the openai-agents SDK's native tracer so it doesn't ship traces to
# api.openai.com (the key may be a gateway/proxy key). Agentex tracing still
# runs via the harness + tracing manager configured in acp.py.
set_tracing_disabled(True)

MODEL_NAME = "gpt-4o"
INSTRUCTIONS = """You are a helpful AI assistant with access to tools.

Current date and time: {timestamp}

Guidelines:
- Be concise and helpful
- Use the weather tool when the user asks about the weather
- Always report the real tool output back to the user
"""


@function_tool
def weather(city: str) -> str:
"""Get the current weather for a city."""
return get_weather(city)


def create_agent() -> Agent:
"""Build and return the OpenAI Agents SDK agent with the weather tool."""
return Agent(
name="Harness OpenAI Assistant",
model=MODEL_NAME,
instructions=INSTRUCTIONS.format(timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
tools=[weather],
)
19 changes: 19 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/project/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Tool definitions for the OpenAI Agents harness tutorial.

The bare function lives here so it's easy to unit-test; it's wrapped as an
OpenAI Agents SDK ``function_tool`` in ``project.agent``.
"""

from __future__ import annotations


def get_weather(city: str) -> str:
"""Get the current weather for a city.

Args:
city: The name of the city to get weather for.

Returns:
A string describing the weather conditions.
"""
return f"The weather in {city} is sunny and 72°F"
36 changes: 36 additions & 0 deletions examples/tutorials/00_sync/060_harness_openai/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "s060-harness-openai"
version = "0.1.0"
description = "A sync OpenAI Agents SDK agent on the unified harness surface"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"agentex-sdk",
"scale-gp",
"openai-agents",
]

[project.optional-dependencies]
dev = [
"pytest",
"pytest-asyncio",
"httpx",
"black",
"isort",
"flake8",
]

[tool.hatch.build.targets.wheel]
packages = ["project"]

[tool.black]
line-length = 88
target-version = ['py312']

[tool.isort]
profile = "black"
line_length = 88
Loading
Loading