182 lines
5.7 KiB
Python
182 lines
5.7 KiB
Python
"""E2E test: OpenAI Agents SDK executor basics (mock LLM).
|
|
|
|
Verifies that the AgentsSdkExecutor runs single-turn and
|
|
multi-turn conversations correctly. An inline agent is registered
|
|
pointing at the mock LLM server; the mock response queue supplies
|
|
the expected answers directly.
|
|
|
|
Both turns route through a runner-bound session — the alpha
|
|
runner-state contract requires ``conversations.runner_id``
|
|
to be set before dispatch, so we create the session and PATCH a
|
|
runner before any ``/events`` POST.
|
|
|
|
Usage::
|
|
|
|
pytest tests/e2e/test_agents_sdk_basic.py -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from tests.e2e.conftest import (
|
|
configure_mock_llm,
|
|
create_runner_bound_session,
|
|
poll_session_until_terminal,
|
|
register_inline_agent,
|
|
reset_mock_llm,
|
|
send_user_message_to_session,
|
|
)
|
|
|
|
|
|
def _extract_all_text(body: dict[str, Any]) -> str:
|
|
"""
|
|
Concatenate all output_text blocks from a response body.
|
|
|
|
:param body: The terminal response body from
|
|
GET /v1/responses/{id}.
|
|
:returns: All assistant text joined by newlines.
|
|
"""
|
|
parts: list[str] = []
|
|
for item in body.get("output", []):
|
|
if item.get("type") == "message":
|
|
for block in item.get("content", []):
|
|
text = block.get("text")
|
|
if text:
|
|
parts.append(text)
|
|
return "\n".join(parts)
|
|
|
|
|
|
def test_agents_sdk_single_turn_completes(
|
|
http_client: httpx.Client,
|
|
live_runner_id: str,
|
|
mock_llm_server_url: str,
|
|
) -> None:
|
|
"""
|
|
Basic smoke test: the Agents SDK executor runs a single
|
|
turn and produces a completed response with correct text.
|
|
|
|
**What breaks if wrong:**
|
|
|
|
- If ``_ensure_sdk()`` fails, ``from_spec`` raises
|
|
``ImportError`` and the task fails immediately.
|
|
- If ``_build_model_settings`` maps config incorrectly,
|
|
the LLM rejects the parameters (400 error).
|
|
- If ``_map_event`` doesn't map ``TextChunk`` correctly,
|
|
no text appears in the response output.
|
|
- If ``TurnComplete`` is never yielded, the response
|
|
stays in ``in_progress`` forever and the poll times out.
|
|
"""
|
|
model = f"mock-basic-single-{uuid.uuid4().hex[:6]}"
|
|
reset_mock_llm(mock_llm_server_url)
|
|
agent_name = register_inline_agent(
|
|
http_client,
|
|
name=f"basic-single-{uuid.uuid4().hex[:6]}",
|
|
harness="openai-agents",
|
|
model=model,
|
|
profile="",
|
|
prompt="You are a helpful math assistant.",
|
|
mock_llm_base_url=f"{mock_llm_server_url}/v1",
|
|
)
|
|
configure_mock_llm(mock_llm_server_url, [{"text": "The answer is 4."}], key=model)
|
|
|
|
session_id = create_runner_bound_session(
|
|
http_client,
|
|
agent_name=agent_name,
|
|
runner_id=live_runner_id,
|
|
)
|
|
response_id = send_user_message_to_session(
|
|
http_client,
|
|
session_id=session_id,
|
|
content="What is 2 + 2? Reply with just the number.",
|
|
)
|
|
body = poll_session_until_terminal(
|
|
http_client,
|
|
session_id=session_id,
|
|
response_id=response_id,
|
|
timeout=60,
|
|
)
|
|
assert body["status"] == "completed", (
|
|
f"Expected completed, got {body['status']}. Error: {body.get('error')}."
|
|
)
|
|
|
|
text = _extract_all_text(body)
|
|
assert "4" in text, f"Expected '4' in response: {text[:300]}"
|
|
|
|
|
|
def test_agents_sdk_multi_turn_remembers(
|
|
http_client: httpx.Client,
|
|
live_runner_id: str,
|
|
mock_llm_server_url: str,
|
|
) -> None:
|
|
"""
|
|
Two-turn conversation: the agent remembers turn 1 content
|
|
in turn 2 via history replay.
|
|
|
|
Turn 1: state a fact. Turn 2: ask about it. Both turns share
|
|
the same session so the second turn dispatches against the
|
|
same persisted conversation items (no ``previous_response_id``
|
|
plumbing needed — same-session continuation is implicit).
|
|
|
|
**What breaks if wrong:**
|
|
|
|
- If ``_messages_to_input`` doesn't pass history correctly,
|
|
the SDK sees no prior context and can't answer.
|
|
- If the workflow doesn't load prior items into ``messages``,
|
|
the executor receives an empty history.
|
|
"""
|
|
model = f"mock-basic-multi-{uuid.uuid4().hex[:6]}"
|
|
reset_mock_llm(mock_llm_server_url)
|
|
agent_name = register_inline_agent(
|
|
http_client,
|
|
name=f"basic-multi-{uuid.uuid4().hex[:6]}",
|
|
harness="openai-agents",
|
|
model=model,
|
|
profile="",
|
|
prompt="You are a helpful assistant.",
|
|
mock_llm_base_url=f"{mock_llm_server_url}/v1",
|
|
)
|
|
configure_mock_llm(
|
|
mock_llm_server_url,
|
|
[
|
|
{"text": "OK, noted."},
|
|
{"text": "Your name is Zephyr and you live in Portland."},
|
|
],
|
|
key=model,
|
|
)
|
|
|
|
session_id = create_runner_bound_session(
|
|
http_client,
|
|
agent_name=agent_name,
|
|
runner_id=live_runner_id,
|
|
)
|
|
|
|
# Turn 1: state a fact.
|
|
id_1 = send_user_message_to_session(
|
|
http_client,
|
|
session_id=session_id,
|
|
content="My name is Zephyr and I live in Portland.",
|
|
)
|
|
body_1 = poll_session_until_terminal(
|
|
http_client, session_id=session_id, response_id=id_1, timeout=60
|
|
)
|
|
assert body_1["status"] == "completed", f"Turn 1 failed: {body_1.get('error')}"
|
|
|
|
# Turn 2: ask about the fact, same session.
|
|
id_2 = send_user_message_to_session(
|
|
http_client,
|
|
session_id=session_id,
|
|
content="What is my name and where do I live?",
|
|
)
|
|
body_2 = poll_session_until_terminal(
|
|
http_client, session_id=session_id, response_id=id_2, timeout=60
|
|
)
|
|
assert body_2["status"] == "completed", f"Turn 2 failed: {body_2.get('error')}"
|
|
|
|
text = _extract_all_text(body_2).lower()
|
|
assert "zephyr" in text, f"Expected 'zephyr' in turn 2 response: {text[:300]}"
|
|
assert "portland" in text, f"Expected 'portland' in turn 2 response: {text[:300]}"
|