Testing
Testing (테스팅)
SDK는 에이전트, 샌드박스 세션, Realtime 세션, Voice 파이프라인을 위한 결정적·제공자 중립적인 테스트 도구를 제공해요. 이 페이지에서는 그 테스트 유틸리티로 어떤 것을 검증할 수 있는지, 그리고 각 시나리오별 레시피를 정리해 드릴게요.
출처: 문서
본문
SDK는 Agent 워크플로우, Sandbox 세션, Realtime 세션, Voice 파이프라인을 위한 결정적이고 제공자 중립적인 테스트 유틸리티를 제공해요. 이 유틸리티들은 메모리 안에서 실행되고, 모델, 샌드박스 제공자, Realtime API 요청을 만들지 않으며, SDK가 소유하는 정규화된 상호작용을 기록해요. 아래 실행 가능한 레시피들은 각 실행의 트레이싱을 비활성화해서, OpenAI API 키가 구성되어 있어도 기본 트레이스 프로세서가 테스트 활동을 업로드하지 않게 해요.
애플리케이션과 SDK가 소유하는 오케스트레이션을 테스트하는 데 사용하세요: 도구 실행, handoff, guardrail, 재시도, 스트리밍, 세션 동작, 샌드박스 능력, Realtime 이벤트 처리, Voice 파이프라인 합성. 외부 모델, 네트워크 프로토콜, 샌드박스 제공자, 또는 오디오 시스템이 소유하는 동작에는 실제 제공자 어댑터나 통합 환경을 사용하세요.
필요한 레시피 찾기
| 원하는 것 | 사용 | 바로가기 |
|---|---|---|
| 고정된 최종 답변 반환 | assistant_message()와 함께 ScriptedModel |
고정 응답 반환 |
| 멀티턴 도구 루프 연습 | function_call() 후 어시스턴트 응답 |
도구 워크플로우 테스트 |
| 요청에서 응답 선택 | ModelStep.respond() 또는 responder 매핑 |
요청에서 응답 파생 |
| 러너가 모델에 보낸 것 검증 | calls, first_call, last_call |
모델 호출 검사 |
| 스트리밍 실행 테스트 | 일반 응답 스텝, 또는 정확한 이벤트를 위한 ModelStep.stream() |
스트리밍 테스트 |
| 오류 또는 재시도 결정 테스트 | ModelStep.raise_error() |
모델 실패 주입 |
| 우연한 워크플로우 변경 감지 | 정확한 FIFO 스텝 + assert_complete() |
워크플로우 드리프트 감지 |
샌드박스 시작 없이 SandboxAgent 테스트 |
scripted_sandbox_session() + ScriptedModel |
샌드박스 에이전트 워크플로우 테스트 |
| 샌드박스 호출 일치 또는 결과 파생 | 샌드박스 스텝의 match 또는 responder |
샌드박스 스텝 구성 |
| 연결 없이 Realtime 세션 테스트 | ScriptedRealtimeModel과 RealtimeStep |
Realtime 세션 테스트 |
| Realtime 도구 워크플로우 테스트 | RealtimeModelToolCallEvent를 내보내고 도구 출력 기대 |
Realtime 도구 워크플로우 테스트 |
| 정적 또는 스트리밍 Voice 파이프라인 테스트 | ScriptedSTTModel, ScriptedTTSModel, 스크립트 또는 실제 워크플로우 |
Voice 파이프라인 테스트 |
| 제공자 직렬화 또는 와이어 페이로드 테스트 | 제어된 네트워크 전송을 가진 실제 제공자 어댑터 | 올바른 경계 고르기 |
임포트
테스트 API는 대체하는 런타임 경계 옆에 있어요:
| 경계 | 임포트 경로 |
|---|---|
| 에이전트 모델과 샌드박스 워크플로우 | agents.testing |
| Realtime 모델 전송 | agents.realtime.testing |
| Voice STT, TTS, 워크플로우 구성 요소 | agents.voice.testing |
테스트 심볼은 의도적으로 최상위 agents 임포트에서 빠져 있어요.
에이전트 워크플로우 레시피
고정 응답 반환
기대되는 각 모델 호출에 대해 정규화된 출력 항목 시퀀스 하나를 전달하세요. 출력 시퀀스 축약 형태는 한 요청에 대해 결정적인 응답 ID와 사용량을 받아요.
import pytest
from agents import Agent, RunConfig, Runner
from agents.testing import ScriptedModel, assistant_message
@pytest.mark.asyncio
async def test_fixed_response() -> None:
model = ScriptedModel(
[[assistant_message("Paris is the capital of France.")]]
)
agent = Agent(name="Geography assistant", model=model)
result = await Runner.run(
agent,
"What is the capital of France?",
run_config=RunConfig(tracing_disabled=True),
)
assert result.final_output == "Paris is the capital of France."
assert len(model.calls) == 1
model.assert_complete()
결정적 워크플로우 테스트는 model.assert_complete()로 마무리하세요. 구성된 모든 스텝을 소비하기 전에 워크플로우가 멈춘 경우를 잡아줘요.
도구 워크플로우 테스트
도구를 호출하는 모델 응답 하나와 최종 답변을 만드는 두 번째 응답을 스크립팅하세요. 실제 SDK 도구 파이프라인이 그 두 모델 호출 사이에서 실행돼요.
import pytest
from agents import Agent, RunConfig, Runner
from agents.decorators import tool
from agents.testing import ScriptedModel, assistant_message, function_call
@tool
def get_weather(city: str) -> str:
"""Return the weather for a city."""
return f"{city}: sunny"
@pytest.mark.asyncio
async def test_tool_workflow() -> None:
model = ScriptedModel(
[
[function_call("get_weather", {"city": "Tokyo"}, call_id="call_1")],
[assistant_message("It is sunny in Tokyo.")],
]
)
agent = Agent(name="Weather assistant", model=model, tools=[get_weather])
result = await Runner.run(
agent,
"What is the weather in Tokyo?",
run_config=RunConfig(tracing_disabled=True),
)
assert result.final_output == "It is sunny in Tokyo."
assert len(model.calls) == 2
assert model.last_call is not None
assert any(
item.get("type") == "function_call_output"
for item in model.last_call.input
)
model.assert_complete()
이 패턴은 도구 입력 검증, 실행, 결과 변환, 훅, guardrail, 그리고 다음 모델 턴을 다뤄요. Python 함수를 직접 호출하면 그런 SDK 동작들을 우회하게 돼요.
요청에서 응답 파생
응답이 정말로 정규화된 모델 호출에 의존하거나, 단언(assertion)이 모델 경계에 속할 때 ModelStep.respond()를 사용하세요. responder는 동기 또는 비동기일 수 있고, ScriptedModel이 받아들이는 어떤 스텝 형태든 반환할 수 있어요.
import pytest
from agents import Agent, RunConfig, Runner
from agents.testing import ModelCall, ModelStep, ScriptedModel, assistant_message
def respond(call: ModelCall):
assert call.streamed is False
assert call.input == [{"content": "Summarize this", "role": "user"}]
return {"output": [assistant_message("Handled the normalized request.")]}
@pytest.mark.asyncio
async def test_request_aware_response() -> None:
model = ScriptedModel([ModelStep.respond(respond)])
agent = Agent(name="Assistant", model=model)
result = await Runner.run(
agent,
"Summarize this",
run_config=RunConfig(tracing_disabled=True),
)
assert result.final_output == "Handled the normalized request."
model.assert_complete()
ScriptedModel은 ModelStep, 동등한 딕셔너리 형태, ModelResponse, 정규화된 출력 항목 시퀀스, 또는 예외를 받아요. 응답이 호출에 의존하지 않을 때는 고정된 출력 시퀀스를 선호하세요. 고정 스크립트는 예상치 못한 턴을 진단하기 더 쉬워지니까요.
모델 호출 검사
ScriptedModel은 각 호출을 해결하거나 선택된 스텝을 발생시키기 전에 기록해요.
| 멤버 | 내용 |
|---|---|
calls |
호출 순서대로의 모든 ModelCall |
first_call |
첫 번째 호출, 또는 None |
last_call |
가장 최근 호출, 또는 None |
remaining_steps |
아직 소비되지 않은 구성된 스텝 수 |
일반적인 단언으로는 call.input, call.model_settings, call.tools, call.handoffs, call.streamed가 있어요. 변경 가능한 요청 데이터는 호출 경계에서 스냅샷되고, 각 공개 기록 접근자는 분리된 스냅샷을 반환해요. 도구, handoff, 출력-스키마, 트레이싱 객체는 런타임 정체성을 유지해요.
구조화된 call_index와 input_index 오류 필드는 0부터 시작해서 calls[...]나 제공된 스텝 시퀀스를 직접 인덱싱해요. 사람이 읽는 오류 메시지는 1부터 시작하는 호출 또는 스텝 번호를 표시해요.
하나의 테스트가 모델 스텝을 점진적으로 덧붙여야 할 때 enqueue()나 extend()를 사용하세요. 독립적인 시나리오에는 새 ScriptedModel을 만드세요. 유틸리티는 소비된 스텝이나 호출 기록을 리셋하지 않아요.
스트리밍 테스트
일반 응답 스텝은 Runner.run()과 Runner.run_streamed() 모두를 지원해요. 일반적인 어시스턴트 메시지, reasoning 항목, 함수 호출, apply-patch 호출에 대해 ScriptedModel은 정규화된 시작, 델타, 항목-완료, 종료 응답 이벤트를 생성해요. 종료 응답은 완전한 출력과 사용량을 담아요.
테스트 대상의 동작이 정확한 정규화된 TResponseStreamEvent 시퀀스일 때만 ModelStep.stream()을 사용하세요:
step = ModelStep.stream(
events,
output=[assistant_message("The terminal output used by the runner.")],
)
events는 고정 시퀀스이거나, 기록된 ModelCall을 받는 비동기 팩토리일 수 있어요. 선택적 output은 같은 스텝이 비스트리밍 호출에서 사용될 때 반환되는 응답이에요. 정확한 스트림 이벤트는 SDK 정규화 이벤트이지 Responses API나 Chat Completions 와이어 청크가 아니에요.
자동 스트리밍은 증분 수명주기가 구현되지 않은 정규화된 출력 항목 종류를 거부해요. 그런 항목에는 부분 이벤트 시퀀스에 의존하는 대신 ModelStep.stream(...)을 사용하세요.
모델 실패 주입
ModelStep.raise_error()를 사용해서 모델 호출 하나를 실패시켜요. 선택적 재시도 조언은 그 정확한 스크립트된 오류에 속해요:
from agents import ModelRetryAdvice
from agents.testing import ModelStep
step = ModelStep.raise_error(
RuntimeError("temporary failure"),
retry_advice=ModelRetryAdvice(suggested=True, replay_safety="safe"),
)
러너의 재시도 정책이 조언이 또 다른 시도를 일으키는지 결정해요. 각 재시도는 또 다른 모델 호출이며 다음으로 스크립트된 스텝을 소비해요. Python 헬퍼는 고정된 ModelRetryAdvice 값을 받아요. 재시도 조언 자체가 시도별로 동적으로 달라져야 한다면 커스텀 Model을 사용하세요.
워크플로우 드리프트 감지
스크립트된 호출을 기대되는 워크플로우 형태로 취급하세요. 추가 모델 요청은 UnexpectedModelCall을 발생시키고, 조기 종료는 assert_complete()이 보고할 스텝을 남겨요.
테스트 프레임워크가 teardown이나 finalizer를 지원한다면, 다른 단언이 실패한 뒤에도 소비되지 않은 스텝을 보고받고 싶을 때 assert_complete()을 거기에 두세요. 일반 회귀 테스트에서는 불일치 오류를 잡지 마세요.
| 오류 | 구조화된 필드 | 의미 |
|---|---|---|
InvalidModelStep |
reason, input_index |
스텝이 잘못 구성되어 큐에 들어가기 전에 거부됨 |
UnexpectedModelCall |
call, call_index |
스크립트가 끝난 뒤 워크플로우가 또 다른 모델 호출을 함 |
UnconsumedModelSteps |
remaining_steps |
워크플로우가 모든 스텝을 사용하기 전에 끝남 |
샌드박스 에이전트 레시피
샌드박스 에이전트 워크플로우 테스트
ScriptedModel과 scripted_sandbox_session()을 결합해서 로컬 컨테이너나 원격 샌드박스를 만들지 않고 실제 SandboxAgent 런타임을 연습해요. 모델 스크립트는 능력 도구를 고르고, 샌드박스 스크립트는 대응하는 SandboxSession 메서드가 무엇을 반환하는지 정의해요.
import pytest
from agents import RunConfig, Runner
from agents.sandbox import ExecResult, SandboxAgent
from agents.sandbox.capabilities import Shell
from agents.testing import (
ScriptedModel,
assistant_message,
function_call,
scripted_sandbox_session,
)
@pytest.mark.asyncio
async def test_sandbox_workflow() -> None:
sandbox = scripted_sandbox_session(
[
{
"method": "exec",
"match": lambda call: call.args == ("pwd",),
"result": ExecResult(
stdout=b"/workspace\n",
stderr=b"",
exit_code=0,
),
}
]
)
model = ScriptedModel(
[
[function_call("exec_command", {"cmd": "pwd"}, call_id="call_1")],
[assistant_message("The workspace is /workspace.")],
]
)
agent = SandboxAgent(
name="Workspace assistant",
model=model,
capabilities=[Shell()],
)
async with sandbox:
result = await Runner.run(
agent,
"Which directory are you in?",
run_config=RunConfig(
sandbox={"session": sandbox},
tracing_disabled=True,
),
)
assert result.final_output == "The workspace is /workspace."
assert [call.method for call in sandbox.calls] == ["exec"]
sandbox.assert_complete()
model.assert_complete()
이 테스트는 두 개의 정규화된 SDK 경계를 가로질러요. 도구 인자 검증, 능력 라우팅, 샌드박스 세션 호출, 다음 모델 턴으로의 도구 결과 전달, 최종 출력 처리를 다뤄요. 실제 모델이 그 명령을 고르는지, 실제 샌드박스 제공자가 어떻게 실행하는지는 테스트하지 않아요.
샌드박스 스텝 구성
각 일치하는 샌드박스 호출은 하나의 전역 FIFO 시퀀스에서 다음 스텝을 소비해요. 메서드 불일치, 매처 거부, 매처 예외는 그 스텝을 대기(pending) 상태로 남겨요. method를 설정하고 정확히 하나의 결과를 고르고, 호출 세부 사항이 중요할 때만 match를 추가하세요.
| 스텝 멤버 | 언제 쓰나... |
|---|---|
result |
메서드가 고정된 타입 값 하나를 반환해야 함 |
responder |
결과가 분리된(detached) SandboxCall에 의존함 |
error |
메서드가 특정 예외를 발생시켜야 함 |
match |
매처가 False가 아닌 값을 반환하지 않는 한, 결과를 내기 전에 호출이 거부되어야 함 |
지원되는 스크립트된 메서드 이름은 apply_patch, exec, ls, mkdir, pty_exec_start, pty_write_stdin, read, rm, write예요. 구성된 모델 직면 능력만 노출돼요. 두 PTY 메서드 중 하나가 구성되면 둘 다 함께 노출돼요. 하나의 인터랙티브 셸 능력을 형성하니까요. 하지만 호출은 여전히 전역 FIFO 스크립트를 소비해요.
sandbox.calls는 0부터 시작하는 call_index, method, 위치 args, 읽기 전용 kwargs를 가진 분리된 SandboxCall 스냅샷을 담아요. 정적 결과도 스크립트가 생성될 때 스냅샷돼요. io.BytesIO와 io.StringIO 값이 지원돼요. 다른 라이브 스트림 객체나 수명주기 동작에는 커스텀 Sandbox 세션을 사용하세요.
| 오류 | 구조화된 필드 | 의미 |
|---|---|---|
InvalidSandboxStep |
reason, input_index, method |
스텝이 잘못 구성되었거나 지원되지 않는 메서드를 이름 붙임 |
UnexpectedSandboxCall |
call, call_index, actual_method, expected_method, remaining_steps |
워크플로우가 잘못된 메서드를 호출하거나 스크립트가 끝난 뒤 계속함 |
SandboxCallMatcherError |
call, call_index, method |
스텝 매처가 False를 반환함 |
UnconsumedSandboxSteps |
remaining_steps, pending_methods |
워크플로우가 모든 스텝을 사용하기 전에 끝남 |
반환되는 객체는 세션 자체예요. RunConfig(sandbox={"session": sandbox})에 직접 전달하세요. 래퍼 .session 속성은 없어요.
Realtime 레시피
Realtime 세션 테스트
ScriptedRealtimeModel은 Python SDK의 정규화된 RealtimeModel 경계를 구현해요. 각 RealtimeStep은 하나의 아웃바운드 RealtimeModelSendEvent와 일치한 다음, 정규화된 인바운드 RealtimeModelEvent 객체를 내보내거나 주입된 오류를 발생시켜요.
import pytest
from agents.realtime import (
RealtimeAgent,
RealtimeModelOutputTextDeltaEvent,
RealtimeModelSendUserInput,
RealtimeRawModelEvent,
RealtimeRunner,
)
from agents.realtime.testing import RealtimeStep, ScriptedRealtimeModel
@pytest.mark.asyncio
async def test_realtime_message() -> None:
reply = RealtimeModelOutputTextDeltaEvent(
item_id="item_1",
delta="Hello!",
response_id="response_1",
)
model = ScriptedRealtimeModel(
[
RealtimeStep(
expect=RealtimeModelSendUserInput(user_input="Hello"),
emit=[reply],
)
]
)
runner = RealtimeRunner(
RealtimeAgent(name="Assistant"),
model=model,
config={"tracing_disabled": True},
)
observed_reply = False
async with await runner.run() as session:
await session.send_message("Hello")
async for event in session:
if isinstance(event, RealtimeRawModelEvent) and event.data == reply:
observed_reply = True
break
assert observed_reply
assert model.sent_events == (RealtimeModelSendUserInput(user_input="Hello"),)
assert model.closed is True
model.assert_complete()
기대(expectation)는 정확한 이벤트 값, isinstance로 일치하는 이벤트 클래스, 또는 아웃바운드 이벤트를 받아 일치하면 True를 반환하는 callable일 수 있어요. 엄격(strict) 모드가 기본으로 활성화돼요. strict=False에서는 무관한 아웃바운드 이벤트가 기록되지만 대기 중인 스텝을 소비하지 않아요. 테스트 대상 동작 밖의 부수 이벤트를 세션이 내보낼 때 유용해요.
connect_events로 연결 중 인바운드 이벤트를 내보내세요. 수명주기 실패에는 connect_error나 close_error를, 일치한 하나의 전송에 묶인 실패에는 RealtimeStep(error=...)을 사용하세요. 스텝은 emit과 error를 둘 다 정의할 수 없어요.
Realtime 도구 워크플로우 테스트
RealtimeAgent에 실제 함수 도구를 붙이고, 정규화된 도구 호출을 내보내며, SDK가 모델 경계를 통해 도구 출력을 보내는 것을 기대하세요. async_tool_calls를 False로 설정하면 이 작은 예시가 테스트별 대기 장치 없이 연결 중에 완료될 수 있어요.
import pytest
from agents.decorators import tool
from agents.realtime import (
RealtimeAgent,
RealtimeModelSendToolOutput,
RealtimeModelToolCallEvent,
RealtimeRunner,
)
from agents.realtime.testing import RealtimeStep, ScriptedRealtimeModel
@tool
def lookup_order(order_id: str) -> str:
"""Look up an order by ID."""
return f"Order {order_id} has shipped."
@pytest.mark.asyncio
async def test_realtime_tool_workflow() -> None:
tool_call = RealtimeModelToolCallEvent(
name="lookup_order",
call_id="call_1",
arguments='{"order_id":"order_123"}',
)
def matches_tool_output(event) -> bool:
return (
isinstance(event, RealtimeModelSendToolOutput)
and event.tool_call.call_id == "call_1"
and event.output == "Order order_123 has shipped."
)
model = ScriptedRealtimeModel(
[RealtimeStep(expect=matches_tool_output)],
connect_events=[tool_call],
)
agent = RealtimeAgent(
name="Order assistant",
tools=[lookup_order],
)
runner = RealtimeRunner(
agent,
model=model,
config={"async_tool_calls": False, "tracing_disabled": True},
)
async with await runner.run():
pass
model.assert_complete()
이것은 실제 Realtime 도구 조회, 인자 검증, 실행, 출력 라우팅을 연습해요. 실제 모델이 그 도구를 고를 것임을 증명하지는 않아요.
Realtime 호출과 수명주기 검사
| 멤버 | 내용 |
|---|---|
connect_calls |
자격 증명이 없는 분리된 연결 스냅샷 |
sent_events |
호출 순서대로의 분리된 아웃바운드 이벤트 스냅샷 |
remaining_steps |
남은 기대 아웃바운드 전송 |
listeners |
현재 등록된 리스너 객체 |
connected, closed, close_calls |
현재 인메모리 수명주기 상태 |
연결 기록은 API 키나 헤더 필드가 제공되었는지만 기록하고, 그 값은 절대 저장하지 않아요. URL 스냅샷은 사용자 정보, 쿼리 매개변수, 프래그먼트를 제거해요. 변경 가능한 이벤트 데이터와 설정은 분리되는 반면, 도구, handoff, 재생 트래커 같은 라이브 SDK 객체는 정체성을 유지해요.
model.assert_complete()로 마무리하고 RealtimeSession 비동기 컨텍스트 매니저가 모델을 닫게 하세요. Python 유틸리티는 의도적으로 대기 중인 기대 프로미스, 암시적 타임아웃, 별도의 assert_closed() 헬퍼를 제공하지 않아요.
| 오류 | 구조화된 필드 | 의미 |
|---|---|---|
UnexpectedRealtimeSend |
actual, expected |
엄격한 아웃바운드 전송이 다음 스텝과 일치하지 않거나 남은 스텝이 없음 |
UnconsumedRealtimeSteps |
remaining_steps |
세션이 모든 기대 전송을 사용하기 전에 끝남 |
RealtimeScriptError |
없음 | 연결이 끊긴 상태에서 보내는 것 같은 잘못된 수명주기 상태로 스크립트가 사용됨 |
Voice 파이프라인 레시피
Voice 파이프라인 테스트
스크립트된 STT·TTS 모델을 SingleAgentVoiceWorkflow와 ScriptedModel로 뒷받침되는 Agent와 결합해서, 제공자 요청 없이 전체 speech-to-text → Agent → text-to-speech 파이프라인을 테스트하세요.
import numpy as np
import pytest
from agents import Agent
from agents.testing import ScriptedModel, assistant_message
from agents.voice import AudioInput, SingleAgentVoiceWorkflow, VoicePipeline
from agents.voice.testing import (
ScriptedSTTModel,
ScriptedTTSModel,
TTSResult,
pcm16_samples,
)
@pytest.mark.asyncio
async def test_voice_pipeline() -> None:
model = ScriptedModel([[assistant_message("Hello there.")]])
stt = ScriptedSTTModel("hello")
pcm = pcm16_samples([0, 100, -100, 0])
tts = ScriptedTTSModel([TTSResult([pcm])])
pipeline = VoicePipeline(
workflow=SingleAgentVoiceWorkflow(
Agent(name="Voice assistant", model=model)
),
stt_model=stt,
tts_model=tts,
config={"tracing_disabled": True, "tts_settings": {"buffer_size": 1}},
)
result = await pipeline.run(AudioInput(np.zeros(2, dtype=np.int16)))
events = [event async for event in result.stream()]
assert events
assert [call.text for call in tts.calls] == ["Hello there."]
stt.assert_complete()
tts.assert_complete()
model.assert_complete()
파이프라인의 STT/TTS 수명주기가 테스트 대상이지만 Agent 오케스트레이션은 아닐 때는 대신 ScriptedVoiceWorkflow를 사용하세요:
from agents.voice.testing import ScriptedVoiceWorkflow
workflow = ScriptedVoiceWorkflow(
turns=["Hello there."],
start="Welcome.",
)
start 스텝은 on_start()이 소비해요. VoicePipeline은 StreamedAudioInput에 대해서만 on_start()을 호출해요. 정적 AudioInput 실행은 start를 소비하지 않아요. 각 일반 턴은 자신의 전사(transcription)를 기록하고 구성된 결과 하나를 소비해요. 문자열은 하나의 프래그먼트이고, 문자열 시퀀스는 텍스트 분할과 TTS 전에 프래그먼트 경계를 제어해요.
스트리밍 전사 테스트
ScriptedSTTModel은 정적 transcriptions와 독립적으로 스크립트된 스트리밍 sessions를 받아요. 세션은 ScriptedTranscriptionSession, 전사 턴 시퀀스, 예외, 또는 단일 문자열일 수 있어요:
from agents.voice.testing import ScriptedSTTModel, ScriptedTranscriptionSession
session = ScriptedTranscriptionSession(["first turn", "second turn"])
stt = ScriptedSTTModel(sessions=[session])
ScriptedTranscriptionSession을 닫으면 반복이 멈추고 건너뛴 턴이 assert_complete()이 보고할 수 있게 남아요. ScriptedTTSModel도 호출당 하나의 TTSResult, 바이트-청크 시퀀스, 또는 예외를 소비해요.
Voice 호출 검사
| 구성 요소 | 기록된 기록 |
|---|---|
ScriptedSTTModel |
calls, session_calls, 라이브 created_sessions 정체성 |
ScriptedTTSModel |
텍스트와 분리된 설정을 담은 calls |
ScriptedVoiceWorkflow |
턴 순서대로의 transcriptions |
정적 오디오 버퍼와 변경 가능한 설정은 호출 시점에 스냅샷돼요. StreamedAudioInput과 생성된 전사-세션 객체는 파이프라인이 계속 사용하므로 라이브 정체성을 유지해요.
| 오류 | 구조화된 필드 | 의미 |
|---|---|---|
UnexpectedVoiceCall |
operation |
정적 전사, 스트리밍 세션, TTS 호출, 워크플로우 시작, 또는 워크플로우 턴에 구성된 스텝이 없음 |
UnconsumedVoiceSteps |
remaining_steps |
하나 이상의 구성된 Voice 스텝이 남음 |
테스트가 구성하는 모든 스크립트된 Voice 구성 요소에 assert_complete()을 호출하세요. ScriptedSTTModel.assert_complete()은 자신이 만든 전사 세션의 턴도 검사해요.
올바른 경계 고르기
테스트가 모델 제공자에 의존하지 않고 SDK 실행 루프, 도구, handoff, guardrail, 세션, 재시도, 또는 정규화된 스트리밍을 연습해야 할 때 ScriptedModel을 사용하세요.
테스트가 샌드박스 제공자를 시작하지 않고 SandboxAgent 능력과 오케스트레이션을 연습해야 할 때 ScriptedModel과 함께 scripted_sandbox_session()을 사용하세요. 제공자 생성, 프로세스 실행, 파일시스템 충실도, 지속성, 리소스 제한, 격리 검사는 실제 샌드박스 제공자에 대한 통합 테스트에 두세요.
테스트가 WebSocket 연결을 열지 않고 RealtimeSession 동작이나 RealtimeAgent 도구·handoff 오케스트레이션을 연습해야 할 때 ScriptedRealtimeModel을 사용하세요. 원시 Realtime 클라이언트/서버 이벤트, 인증, 네트워크 복구, 오디오 전송 동작은 실제 전송 또는 통합 환경에 두세요. Realtime API 세션은 클라이언트가 입력을 보내고 이벤트를 받는 동안 연결을 열어두므로, 그 네트워크·프로토콜 문제는 정규화된 모델 경계 아래에 속해요. 프로덕션 연결 아키텍처는 OpenAI Realtime API 가이드를 참고하세요.
테스트가 음성 제공자 없이 STT/TTS 순서, 스트리밍 전사 정리, 워크플로우 프래그먼트 전달, 또는 완전한 Voice 파이프라인 합성을 연습해야 할 때 Voice 테스트 구성 요소를 사용하세요. 전사 품질, 생성된 음성, 인코딩 호환성, 지연, 또는 재생이 테스트 대상일 때는 실제 오디오 모델과 대표적인 오디오를 사용하세요.
이 유틸리티로 Responses API나 Chat Completions 요청 직렬화, 인증 헤더, 제공자 기본값, HTTP 페이로드, 제공자 스트림 청크, Realtime 와이어 프레임, 제공자별 수명주기 동작을 테스트하지 마세요. 그런 테스트에는 실제 어댑터를 유지하고 네트워크 경계를 교체하거나 제어하세요. openai v3에서는 OpenAI 어댑터 테스트가 httpx2 요청, 응답, 전송, 예외 타입을 사용해야 해요. 레거시 httpx는 Agents SDK의 핵심 의존성이 아니에요.
최종 체크리스트
- 정규화된 모델, 샌드박스 세션, Realtime 모델, 또는 Voice 파이프라인 경계가 소유하는 상호작용만 스크립팅하세요.
- 프라이빗 러너 상태 대신 중요한 공개 요청·호출 필드를 단언하세요.
- 고정된 응답 스텝을 선호하고, 요청 의존 동작에만 responder를 사용하세요.
- 자동 모델 스트리밍을 선호하고, 이벤트 수준 동작이 중요할 때만 정확한 스트림을 사용하세요.
- 각 스크립트된 구성 요소 테스트를 그
assert_complete()메서드로 끝내세요. - 주변 테스트가 그 수명주기를 소유할 때 Realtime과 Sandbox 정리에는 비동기 컨텍스트 매니저를 사용하세요.
- 사람이 읽는 메시지를 파싱하는 대신 구조화된 오류 필드를 단언하세요.
- 제공자 와이어 테스트는 제어된 네트워크 전송을 가진 실제 어댑터에 두세요.
범위와 현재 제한 사항
테스트 모듈은 의도적으로 다음을 제공하지 않아요:
- 모든 정규화된 모델 출력 항목을 위한 편의 빌더. 일반적인 경우에
assistant_message()와function_call()을 사용하고, 다른 정규화된 항목은 직접 전달하세요. - 제공자-프로토콜 시뮬레이터. 정확한 모델 스트림은 Responses API나 Chat Completions 와이어 청크가 아닌 정규화된 SDK 이벤트를 사용해요.
- 고수준 시뮬레이션된 Realtime 서버. 테스트는 정규화된 아웃바운드 전송을 명시적으로 일치시키고 시나리오에 필요한 정규화된 인바운드 이벤트를 내보내요.
- 순서 없는 샌드박스 또는 Realtime 기대. 두 유틸리티 모두 기대 스텝을 하나의 전역 순서로 소비해요.
- 테스트 러너별 매처, 픽스처, 암시적 타임아웃, 자동 teardown.
- 리셋 API.
ScriptedModel은 점진적 스크립트용enqueue()와extend()를 지원하지만, 독립 시나리오에는 새 스크립트 구성 요소를 만드세요.
테스트가 잘못된 스트림, 제어된 일시 중지·동시성, 정확한 취소, 또는 스크립트된 유틸리티가 보존할 수 없는 수명주기 경계를 요구할 때는 해당 공개 인터페이스의 커스텀 구현을 사용하세요. 그 특수 경계를 테스트에 문서화하세요.
API 레퍼런스
더 알아보기 (Learn more)
- OpenAI Agents SDK 문서에서 테스트 유틸리티와 CLI 마찰을 확인하세요.
- pytest에서 비동기 테스트 실행과 픽스처를 참고하세요.