Realtime agents guide
Realtime agents guide (Realtime 에이전트 가이드)
이 가이드는 OpenAI Agents SDK의 realtime 계층이 OpenAI Realtime API에 어떻게 매핑되는지, 그리고 Python SDK가 그 위에 어떤 추가 동작을 얹는지 설명해요.
여기서 시작하기: 기본 Python 경로를 원한다면 먼저 quickstart를 읽으세요. 앱이 서버 측 WebSocket과 SIP 중 무엇을 써야 할지 결정 중이라면 Realtime transport를 읽으세요. 브라우저 WebRTC transport는 Python SDK의 일부가 아니에요.
출처: 문서
본문
개요
Realtime 에이전트는 Realtime API에 오래 지속되는 연결을 유지해서, 모델이 텍스트와 오디오를 점진적으로 처리하고 오디오 출력을 스트리밍하며 도구를 호출하고 interruption을 처리할 수 있어요. 매 턴마다 새 요청을 다시 시작할 필요 없이요.
주요 SDK 구성 요소:
RealtimeAgent— 한 realtime 전문가를 위한 지시·도구·출력 guardrail·handoffRealtimeRunner— 시작 에이전트를 realtime transport에 연결하는 세션 팩토리RealtimeSession— 입력 보내기·이벤트 받기·기록 추적·도구 실행을 하는 라이브 세션RealtimeModel— transport 추상화. 기본은 OpenAI의 서버 측 WebSocket 구현.
세션 lifecycle
일반적인 realtime 세션은 이렇게 생겼어요.
- 하나 이상의
RealtimeAgent를 만든다. - 시작 에이전트로
RealtimeRunner를 만든다. await runner.run()으로RealtimeSession을 얻는다.async with session:또는await session.enter()로 세션에 진입한다.send_message()나send_audio()로 사용자 입력을 보낸다.- 대화가 끝날 때까지 세션 이벤트를 반복한다.
텍스트 전용 실행과 달리 runner.run()은 즉시 최종 결과를 만들지 않아요. 로컬 기록·백그라운드 도구 실행·guardrail 상태·활성 에이전트 구성을 transport 계층과 동기화하면서 유지하는 라이브 세션 객체를 반환해요.
기본적으로 RealtimeRunner는 OpenAIRealtimeWebSocketModel을 사용하므로, 기본 Python 경로는 Realtime API에 대한 서버 측 WebSocket 연결이에요. 다른 RealtimeModel을 넘기면 연결 메커니즘은 바뀔 수 있지만 같은 세션 lifecycle과 에이전트 기능이 적용돼요.
Realtime API 서버가 기본 WebSocket 연결을 정상적으로 닫으면 모델 transport는 disconnected RealtimeModelConnectionStatusEvent에 이어 RealtimeModelEndOfStreamEvent를 방출해요. RealtimeSession은 둘 다 raw_model_event 안에서 전달하고, 이미 큐에 있는 이벤트를 배출한 뒤 예외를 발생시키지 않고 async 반복을 끝내요. 호출자가 시작한 session.close()는 이런 서버 연결 해제 이벤트를 합성하지 않아요. 예상치 못한 WebSocket 실패는 정상 서버 닫기처럼 반복을 끝내는 대신 세션의 예외 경로를 계속 진행해요.
에이전트와 세션 구성
RealtimeAgent는 의도적으로 일반 Agent 타입보다 좁아요.
- 모델 선택은 에이전트별이 아니라 세션 수준에서 구성돼요.
- 구조화 출력은 지원되지 않아요.
- 음성은 구성할 수 있지만, 세션이 이미 음성 오디오를 만든 뒤에는 바꿀 수 없어요.
- 지시·function tool·handoff·훅·출력 guardrail은 여전히 동작해요.
RealtimeSessionModelSettings는 더 새로운 중첩 audio 구성과 더 오래된 평평한 별칭을 둘 다 지원해요. 새 코드에는 중첩 형태를, 새 realtime 에이전트에는 gpt-realtime-2.1로 시작하세요.
runner = RealtimeRunner(
starting_agent=agent,
config={
"model_settings": {
"model_name": "gpt-realtime-2.1",
"audio": {
"input": {
"format": "pcm16",
"transcription": {"model": "gpt-4o-mini-transcribe"},
"turn_detection": {"type": "semantic_vad", "interrupt_response": True},
},
"output": {"format": "pcm16", "voice": "ash"},
},
"tool_choice": "auto",
}
},
)
유용한 세션 수준 설정:
audio.input.format,audio.output.formataudio.input.transcriptionaudio.input.noise_reductionaudio.input.turn_detectionaudio.output.voice,audio.output.speedoutput_modalitiestool_choiceprompttracing
RealtimeRunner(config=...)의 유용한 실행 수준 설정:
async_tool_callsoutput_guardrailsguardrails_settings.debounce_text_lengthtool_error_formattertracing_disabled
전체 타이핑된 표면은 RealtimeRunConfig와 RealtimeSessionModelSettings를 참고하세요.
입력 전사 설정
audio.input.transcription 아래에서 입력 전사를 구성하세요. 저지연 증분 대본(transcript)에는 gpt-live-transcribe를, 오디오 턴이 커밋된 뒤 전사를 시작하거나 감지된 언어 출력이 필요하면 WebSocket 위 gpt-transcribe를 쓰세요. Agents SDK는 중첩 세션 구성에서 모델별 GA 전사 설정을 전달해요.
runner = RealtimeRunner(
starting_agent=agent,
config={
"model_settings": {
"audio": {
"input": {
"transcription": {
"model": "gpt-live-transcribe",
"prompt": "A support call about the OpenAI Agents SDK.",
"keywords": ["RunState", "MCPServerManager"],
"languages": ["en", "ja"],
},
"turn_detection": None,
}
}
}
},
)
gpt-live-transcribe의 경우 prompt는 자유 형식 녹음 컨텍스트를 제공하고, keywords는 오디오에 나타날 수 있는 리터럴 용어를 나열하며, languages는 예상 입력 언어를 나열해요. 이 모델은 단수 language 대신 복수 languages를 사용해요. 두 필드를 함께 보내지 마세요.
이 SDK가 고정한 OpenAI 클라이언트 버전은 delay를 gpt-realtime-whisper에서만 지원해요. 그 모델의 지연·정확도 트레이드오프를 이렇게 구성하세요.
runner = RealtimeRunner(
starting_agent=agent,
config={
"model_settings": {
"audio": {
"input": {
"transcription": {
"model": "gpt-realtime-whisper",
"delay": "low",
},
"turn_detection": None,
}
}
}
},
)
delay 설정은 minimal, low, medium, high, xhigh를 받아요. 값이 낮을수록 더 이른 부분 텍스트를 만들 수 있고, 값이 높을수록 전사 모델에 더 많은 오디오 컨텍스트를 줘 인식 정확도를 높일 수 있어요. 어떤 수준에 대해서도 고정된 타이밍을 가정하지 말고 대표 오디오를 벤치마킹하세요.
gpt-transcribe를 Realtime 세션에서 WebSocket 위로 쓰는 것은 전사가 커밋된 오디오 턴 뒤에 시작되어야 하거나 애플리케이션이 감지된 언어 출력이 필요할 때만 하세요. 모델은 이전에 전사된 턴을 자동으로 컨텍스트로 사용해요. gpt-transcribe 완료 이벤트는 languages 출력 필드에 감지된 언어를 보고해요. 이 출력 필드는 위에 보인 gpt-live-transcribe 예상 언어 입력과는 달라요.
audio.input.turn_detection을 None으로 설정하면 자동 턴 감지를 비활성화해요. 그러면 애플리케이션이 Manual response control에 설명된 대로 오디오 턴을 커밋하고 응답 생성을 제어해야 해요. 모델 동작·검증 규칙·지연 안내는 OpenAI API Realtime transcription 가이드를 참고하세요.
입력과 출력
텍스트와 구조화 사용자 메시지
일반 텍스트나 구조화된 realtime 메시지에는 session.send_message()을 쓰세요.
from agents.realtime import RealtimeUserInputMessage
await session.send_message("Summarize what we discussed so far.")
message: RealtimeUserInputMessage = {
"type": "message",
"role": "user",
"content": [
{"type": "input_text", "text": "Describe this image."},
{"type": "input_image", "image_url": image_data_url, "detail": "high"},
],
}
await session.send_message(message)
구조화 메시지는 realtime 대화에 이미지 입력을 포함하는 주요 방법이에요. examples/realtime/app/server.py의 예제 웹 데모는 이런 방식으로 input_image 메시지를 전달해요.
오디오 입력
원시 오디오 바이트를 스트리밍하려면 session.send_audio()를 쓰세요.
await session.send_audio(audio_bytes)
서버 측 턴 감지가 꺼져 있다면 턴 경계를 표시하는 책임은 당신에게 있어요. 고수준 편의 방법은:
await session.send_audio(audio_bytes, commit=True)
더 저수준 제어가 필요하다면 기본 모델 transport를 통해 input_audio_buffer.commit 같은 Realtime API 클라이언트 이벤트를 직접 보낼 수도 있어요.
수동 응답 제어
session.send_message()은 고수준 경로로 사용자 입력을 보내고 당신을 위해 응답을 시작해요. 일부 구성에서는 원시 오디오 버퍼링이 자동으로 그렇게 하지 않아요.
Realtime API 수준에서 수동 턴 제어는 turn_detection을 null로 설정하는 session.update 이벤트를 보낸 뒤, 직접 input_audio_buffer.commit과 response.create를 보내는 것을 의미해요.
턴을 수동으로 관리한다면 모델 transport를 통해 원시 클라이언트 이벤트를 보낼 수 있어요.
from agents.realtime.model_inputs import RealtimeModelSendRawMessage
await session.model.send_event(
RealtimeModelSendRawMessage(
message={
"type": "response.create",
}
)
)
이 패턴은 유용한 경우:
turn_detection이 꺼져 있고 모델이 언제 응답해야 할지 직접 정하고 싶을 때- 응답을 트리거하기 전에 사용자 입력을 검사·게이트하고 싶을 때
- 대역 외(out-of-band) 응답에 커스텀 prompt가 필요할 때
examples/realtime/twilio_sip/server.py의 SIP 예제는 시작 인사를 강제하기 위해 원시 response.create를 사용해요.
이벤트, 기록, interruption
RealtimeSession은 더 높은 수준의 SDK 이벤트를 방출하면서, 필요할 때 원시 모델 이벤트도 계속 전달해요.
가치 있는 세션 이벤트:
audio,audio_end,audio_interruptedagent_start,agent_endtool_start,tool_end,tool_approval_requiredhandoffhistory_added,history_updatedguardrail_trippedinput_audio_timeout_triggerederrorraw_model_event
UI 상태에 가장 유용한 이벤트는 보통 history_added와 history_updated예요. 세션의 로컬 기록을 사용자 메시지·어시스턴트 메시지·도구 호출을 포함한 RealtimeItem 객체로 노출해요.
사용량 회계
완료된 모델 응답이 사용량을 포함하면 SDK의 OpenAI RealtimeModel transport가 raw_model_event 안에서 RealtimeModelUsageEvent를 방출해요. 그 usage 필드는 그 응답의 토큰 수를 담고, input_tokens_details와 output_tokens_details는 선택적 모달리티별 세부 정보를 제공해요.
세션은 또한 각 응답의 사용량을 공유 RunContextWrapper.usage에 더해요. agent_end 같은 후속 고수준 이벤트에서 event.info.context.usage를 읽어 라이브 세션의 누적 사용량을 검사할 수 있어요.
from agents.realtime import RealtimeModelUsageEvent
async for event in session:
if event.type == "raw_model_event" and isinstance(
event.data, RealtimeModelUsageEvent
):
response_usage = event.data.usage
print("Response tokens:", response_usage.total_tokens)
print("Input modalities:", event.data.input_tokens_details)
print("Output modalities:", event.data.output_tokens_details)
elif event.type == "agent_end":
session_usage = event.info.context.usage
print("Session tokens:", session_usage.total_tokens)
사용량은 모델 프로바이더가 완료 응답에 포함할 때만 보고돼요. 누적 값은 그 RealtimeSession이 받은 응답을 다루는 것으로, 세션 간 합계가 아니에요.
Interruption과 재생 추적
사용자가 어시스턴트를 중단하면 세션은 audio_interrupted를 방출하고 기록을 갱신해서 서버 측 대화가 사용자가 실제로 들은 것과 정렬되게 해요.
저지연 로컬 재생에서는 기본 재생 추적기로 충분한 경우가 많아요. 원격·지연 재생 시나리오, 특히 텔레포니에서는 RealtimePlaybackTracker를 써서 중단된 응답이 모든 생성된 오디오가 이미 들렸다고 가정하지 않고 실제 재생 위치에서 잘리게 하세요.
examples/realtime/twilio/twilio_handler.py의 Twilio 예제는 이 패턴을 보여줘요.
도구, 승인, handoff, guardrail
Function tools
Realtime 에이전트는 라이브 대화 중 function tool을 지원해요.
from agents.decorators import tool
@tool
def get_weather(city: str) -> str:
"""Get current weather for a city."""
return f"The weather in {city} is sunny, 72F."
agent = RealtimeAgent(
name="Assistant",
instructions="You can answer weather questions.",
tools=[get_weather],
)
도구 승인
function tool은 실행 전에 인간 승인을 요구할 수 있어요. 그럴 때 세션은 tool_approval_required를 방출하고, approve_tool_call()이나 reject_tool_call()을 호출할 때까지 도구 실행을 일시 중지해요.
도구에 입력 guardrail도 있다면 그 guardrail은 승인 후 실행 직전에 실행돼요. 승인 이벤트가 방출되기 전에 실행하려면 RealtimeRunner(..., config={"tool_execution": {"pre_approval_tool_input_guardrails": True}})로 러너를 만드세요. 이 사전 승인 검사를 통과한 호출도 실행 전에 승인 후 다시 검사돼요.
async for event in session:
if event.type == "tool_approval_required":
await session.approve_tool_call(event.call_id)
구체적인 서버 측 승인 루프는 examples/realtime/app/server.py를 참고하세요. human-in-the-loop 문서도 이 흐름을 가리켜요. Human in the loop 참고.
Handoffs
Realtime handoff는 한 에이전트가 라이브 대화를 다른 전문가에게 전송하게 해줘요.
from agents.realtime import RealtimeAgent, realtime_handoff
billing_agent = RealtimeAgent(
name="Billing Support",
instructions="You specialize in billing issues.",
)
main_agent = RealtimeAgent(
name="Customer Service",
instructions="Triage the request and hand off when needed.",
handoffs=[
realtime_handoff(
billing_agent,
tool_description_override="Transfer to billing support",
)
],
)
handoff로 직접 사용되는 RealtimeAgent 객체는 자동으로 감싸지고, realtime_handoff(...)는 이름·설명·검증·콜백·가용성을 커스터마이즈하게 해줘요. Realtime handoff는 일반 handoff input_filter를 지원하지 않아요.
Guardrails
Realtime 에이전트는 에이전트 응답의 출력 guardrail과 function-tool 호출의 입력 guardrail을 지원해요. 출력 guardrail 검사는 디바운스(debounce)돼요. 각 검사는 모든 부분 delta가 아니라 누적된 출력 텍스트·오디오 대본 delta에 대해 실행되고, 예외를 발생시키는 대신 guardrail_tripped를 방출해요. 단일 delta는 검사를 최대 하나 스케줄해요. 그 delta가 여러 debounce_text_length 경계를 넘으면 SDK는 나중 작은 delta 뒤에 따라잡기 검사를 스케줄하는 대신 다음 경계를 전부 넘어 진행시켜요.
from agents.guardrail import GuardrailFunctionOutput, OutputGuardrail
def sensitive_data_check(context, agent, output):
return GuardrailFunctionOutput(
tripwire_triggered="password" in output,
output_info=None,
)
agent = RealtimeAgent(
name="Assistant",
instructions="...",
output_guardrails=[OutputGuardrail(guardrail_function=sensitive_data_check)],
)
realtime 출력 guardrail이 오디오 대본에서 걸리면 세션은 활성 응답을 interrupt하고, response.cancel을 강제하며, guardrail_tripped를 방출하고, 트리거된 guardrail을 명명하는 후속 사용자 메시지를 보내 모델이 교체 응답을 만들게 해요. tripwire가 발화할 때 일부 오디오는 이미 버퍼링될 수 있으니 오디오 플레이어는 여전히 audio_interrupted를 듣고 로컬 재생을 즉시 멈춰야 해요. 내장 OpenAI Realtime transport에서 guardrail 검사가 검사 중인 응답이 끝난 뒤에 완료되면, 세션은 그 응답의 버퍼링된 재생만 interrupt하고 나중에 시작된 응답은 취소하지 않아요. 텍스트 전용 출력에서는 세션은 대신 response-scoped response.cancel을 보내요. 멈출 오디오 재생이 없으므로 audio_interrupted는 방출하지 않아요. 내장 OpenAI Realtime 모델을 쓸 때 텍스트 전용 경로에서도 같은 guardrail_tripped 이벤트와 후속 사용자 메시지가 방출돼요.
커스텀 RealtimeModel transport는 같은 소스 범위의 오디오 중단 동작을 제공하려면 RealtimeModelSendInterrupt.response_id와 playback_only를 지켜야 해요. 텍스트 전용 출력 경로의 복구 메시지를 지원하려면 RealtimeModel.send_event_if()도 재정의해야 해요. 구현은 공급된 조건을 transport의 실제 이벤트 커밋 경계에서 다시 확인하거나, 조건 검사를 이벤트 커밋과 함께 직렬화해야 해요. 기본 구현은 복구 메시지를 안전하게 건너뛰는데, 조건을 한 번 확인하고 이벤트를 별도로 보내면 그 검사와 이벤트 커밋 사이에 다른 응답이 시작될 수 있기 때문이에요. response.cancel과 guardrail_tripped 이벤트는 여전히 발생해요.
SIP와 텔레포니
Python SDK는 OpenAIRealtimeSIPModel을 통한 일급(first-class) SIP 연결 흐름을 포함해요. Realtime Calls API를 통해 호출이 도착하고 결과 call_id에 에이전트 세션을 연결하고 싶을 때 쓰세요.
from agents.realtime import RealtimeRunner
from agents.realtime.openai_realtime import OpenAIRealtimeSIPModel
runner = RealtimeRunner(starting_agent=agent, model=OpenAIRealtimeSIPModel())
async with await runner.run(
model_config={
"call_id": call_id_from_webhook,
}
) as session:
async for event in session:
...
먼저 호출을 수락해야 하고 수락 페이로드가 에이전트 파생 세션 구성과 일치하길 원한다면 OpenAIRealtimeSIPModel.build_initial_session_payload(...)을 사용하세요. 전체 흐름은 examples/realtime/twilio_sip/server.py에 나와 있어요.
저수준 접근과 커스텀 엔드포인트
session.model로 기본 transport 객체에 접근할 수 있어요. 다음과 같은 것이 필요할 때 쓰세요.
session.model.add_listener(...)를 통한 커스텀 리스너response.create나session.update같은 원시 클라이언트 이벤트model_config를 통한 커스텀url·headers·api_key처리- 기존 realtime 호출에
call_id연결
RealtimeModelConfig는 지원해요.
api_keyurlheadersinitial_model_settingsplayback_trackercall_id
이 저장소가 제공하는 call_id 예제는 SIP예요. 더 넓은 Realtime API도 일부 서버 측 제어 흐름에 call_id를 쓰지만, 그것들은 여기 Python 예제로 패키징돼 있지 않아요.
Azure OpenAI에 연결할 때는 GA Realtime 엔드포인트 URL과 명시적 헤더를 넘기세요. 예를 들어.
session = await runner.run(
model_config={
"url": "wss://<your-resource>.openai.azure.com/openai/v1/realtime?model=<deployment-name>",
"headers": {"api-key": "<your-azure-api-key>"},
}
)
토큰 기반 인증에는 headers에 bearer 토큰을 쓰세요.
session = await runner.run(
model_config={
"url": "wss://<your-resource>.openai.azure.com/openai/v1/realtime?model=<deployment-name>",
"headers": {"authorization": f"Bearer {token}"},
}
)
headers를 넘기면 SDK가 Authorization을 자동으로 추가하지 않아요. realtime 에이전트에는 레거시 베타 경로(/openai/realtime?api-version=...)를 피하세요.
더 읽을 거리
- Realtime transport
- Quickstart
- OpenAI Realtime conversations
- OpenAI Realtime server-side controls
examples/realtime
더 알아보기 (Learn more)
- OpenAI Agents SDK 문서에서 더 많은 가이드를 확인하세요.