feat: default realtime sessions to gpt-realtime-2 (#3190)
This commit is contained in:
@@ -128,13 +128,21 @@ triage_agent = Agent[AirlineAgentContext](
|
||||
"You are a helpful triaging agent. You can use your tools to delegate questions to other appropriate agents."
|
||||
),
|
||||
handoffs=[
|
||||
faq_agent,
|
||||
handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff),
|
||||
handoff(agent=faq_agent, tool_name_override="transfer_to_faq_agent"),
|
||||
handoff(
|
||||
agent=seat_booking_agent,
|
||||
on_handoff=on_seat_booking_handoff,
|
||||
tool_name_override="transfer_to_seat_booking_agent",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
faq_agent.handoffs.append(triage_agent)
|
||||
seat_booking_agent.handoffs.append(triage_agent)
|
||||
faq_agent.handoffs.append(
|
||||
handoff(agent=triage_agent, tool_name_override="transfer_to_triage_agent")
|
||||
)
|
||||
seat_booking_agent.handoffs.append(
|
||||
handoff(agent=triage_agent, tool_name_override="transfer_to_triage_agent")
|
||||
)
|
||||
|
||||
|
||||
### RUN
|
||||
|
||||
@@ -15,8 +15,6 @@ will use the agent returned from get_starting_agent() as the starting agent."""
|
||||
name_override="faq_lookup_tool", description_override="Lookup frequently asked questions."
|
||||
)
|
||||
async def faq_lookup_tool(question: str) -> str:
|
||||
print("faq_lookup_tool called with question:", question)
|
||||
|
||||
# Simulate a slow API call
|
||||
await asyncio.sleep(3)
|
||||
|
||||
@@ -91,11 +89,18 @@ triage_agent = RealtimeAgent(
|
||||
"You are a helpful triaging agent. You can use your tools to delegate questions to other appropriate agents."
|
||||
),
|
||||
tools=[get_weather],
|
||||
handoffs=[faq_agent, realtime_handoff(seat_booking_agent)],
|
||||
handoffs=[
|
||||
realtime_handoff(faq_agent, tool_name_override="transfer_to_faq_agent"),
|
||||
realtime_handoff(seat_booking_agent, tool_name_override="transfer_to_seat_booking_agent"),
|
||||
],
|
||||
)
|
||||
|
||||
faq_agent.handoffs.append(triage_agent)
|
||||
seat_booking_agent.handoffs.append(triage_agent)
|
||||
faq_agent.handoffs.append(
|
||||
realtime_handoff(triage_agent, tool_name_override="transfer_to_triage_agent")
|
||||
)
|
||||
seat_booking_agent.handoffs.append(
|
||||
realtime_handoff(triage_agent, tool_name_override="transfer_to_triage_agent")
|
||||
)
|
||||
|
||||
|
||||
def get_starting_agent() -> RealtimeAgent:
|
||||
|
||||
@@ -52,7 +52,7 @@ class RealtimeWebSocketManager:
|
||||
# runner = RealtimeRunner(agent, config=runner_config)
|
||||
model_config: RealtimeModelConfig = {
|
||||
"initial_model_settings": {
|
||||
"model_name": "gpt-realtime-1.5",
|
||||
"model_name": "gpt-realtime-2",
|
||||
"turn_detection": {
|
||||
"type": "server_vad",
|
||||
"prefix_padding_ms": 300,
|
||||
|
||||
@@ -225,7 +225,7 @@ class NoUIDemo:
|
||||
model_config: RealtimeModelConfig = {
|
||||
"playback_tracker": self.playback_tracker,
|
||||
"initial_model_settings": {
|
||||
"model_name": "gpt-realtime-1.5",
|
||||
"model_name": "gpt-realtime-2",
|
||||
"turn_detection": {
|
||||
"type": "semantic_vad",
|
||||
"interrupt_response": True,
|
||||
|
||||
@@ -93,7 +93,7 @@ class TwilioHandler:
|
||||
model_config={
|
||||
"api_key": api_key,
|
||||
"initial_model_settings": {
|
||||
"model_name": "gpt-realtime-1.5",
|
||||
"model_name": "gpt-realtime-2",
|
||||
"input_audio_format": "g711_ulaw",
|
||||
"output_audio_format": "g711_ulaw",
|
||||
"turn_detection": {
|
||||
|
||||
@@ -74,11 +74,18 @@ triage_agent = RealtimeAgent(
|
||||
"before collecting details. Once the greeting is complete, gather context and hand off to "
|
||||
"the FAQ or Records agents when appropriate."
|
||||
),
|
||||
handoffs=[faq_agent, realtime_handoff(records_agent)],
|
||||
handoffs=[
|
||||
realtime_handoff(faq_agent, tool_name_override="transfer_to_faq_agent"),
|
||||
realtime_handoff(records_agent, tool_name_override="transfer_to_records_agent"),
|
||||
],
|
||||
)
|
||||
|
||||
faq_agent.handoffs.append(triage_agent)
|
||||
records_agent.handoffs.append(triage_agent)
|
||||
faq_agent.handoffs.append(
|
||||
realtime_handoff(triage_agent, tool_name_override="transfer_to_triage_agent")
|
||||
)
|
||||
records_agent.handoffs.append(
|
||||
realtime_handoff(triage_agent, tool_name_override="transfer_to_triage_agent")
|
||||
)
|
||||
|
||||
|
||||
def get_starting_agent() -> RealtimeAgent:
|
||||
|
||||
@@ -69,7 +69,7 @@ async def accept_call(call_id: str) -> None:
|
||||
f"/realtime/calls/{call_id}/accept",
|
||||
body={
|
||||
"type": "realtime",
|
||||
"model": "gpt-realtime-1.5",
|
||||
"model": "gpt-realtime-2",
|
||||
"instructions": instructions_payload,
|
||||
},
|
||||
cast_to=dict,
|
||||
|
||||
@@ -7,6 +7,8 @@ from .config import (
|
||||
RealtimeInputAudioTranscriptionConfig,
|
||||
RealtimeModelName,
|
||||
RealtimeModelTracingConfig,
|
||||
RealtimeReasoningConfig,
|
||||
RealtimeReasoningEffort,
|
||||
RealtimeRunConfig,
|
||||
RealtimeSessionModelSettings,
|
||||
RealtimeTurnDetectionConfig,
|
||||
@@ -108,6 +110,8 @@ __all__ = [
|
||||
"RealtimeInputAudioTranscriptionConfig",
|
||||
"RealtimeModelName",
|
||||
"RealtimeModelTracingConfig",
|
||||
"RealtimeReasoningConfig",
|
||||
"RealtimeReasoningEffort",
|
||||
"RealtimeRunConfig",
|
||||
"RealtimeSessionModelSettings",
|
||||
"RealtimeTurnDetectionConfig",
|
||||
|
||||
@@ -20,6 +20,7 @@ RealtimeModelName: TypeAlias = (
|
||||
Literal[
|
||||
"gpt-realtime",
|
||||
"gpt-realtime-1.5",
|
||||
"gpt-realtime-2",
|
||||
"gpt-realtime-2025-08-28",
|
||||
"gpt-4o-realtime-preview",
|
||||
"gpt-4o-realtime-preview-2024-10-01",
|
||||
@@ -45,6 +46,10 @@ RealtimeAudioFormat: TypeAlias = (
|
||||
"""The audio format for realtime audio streams."""
|
||||
|
||||
|
||||
RealtimeReasoningEffort: TypeAlias = Literal["minimal", "low", "medium", "high", "xhigh"] | str
|
||||
"""The reasoning effort for realtime model responses."""
|
||||
|
||||
|
||||
class RealtimeClientMessage(TypedDict):
|
||||
"""A raw message to be sent to the model."""
|
||||
|
||||
@@ -130,6 +135,13 @@ class RealtimeAudioConfig(TypedDict, total=False):
|
||||
output: RealtimeAudioOutputConfig
|
||||
|
||||
|
||||
class RealtimeReasoningConfig(TypedDict, total=False):
|
||||
"""Reasoning configuration for realtime sessions."""
|
||||
|
||||
effort: RealtimeReasoningEffort
|
||||
"""The reasoning effort to use for realtime model responses."""
|
||||
|
||||
|
||||
class RealtimeSessionModelSettings(TypedDict):
|
||||
"""Model settings for a realtime model session."""
|
||||
|
||||
@@ -175,6 +187,12 @@ class RealtimeSessionModelSettings(TypedDict):
|
||||
tool_choice: NotRequired[ToolChoice]
|
||||
"""How the model should choose which tools to call."""
|
||||
|
||||
parallel_tool_calls: NotRequired[bool]
|
||||
"""Whether the model may make parallel tool calls."""
|
||||
|
||||
reasoning: NotRequired[RealtimeReasoningConfig]
|
||||
"""Reasoning configuration for realtime model responses."""
|
||||
|
||||
tools: NotRequired[list[Tool]]
|
||||
"""List of tools available to the model."""
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ OpenAIRealtimeAudioOutput = _rt_audio_config.RealtimeAudioConfigOutput # type:
|
||||
|
||||
|
||||
_USER_AGENT = f"Agents/Python {__version__}"
|
||||
DEFAULT_REALTIME_MODEL = "gpt-realtime-1.5"
|
||||
DEFAULT_REALTIME_MODEL = "gpt-realtime-2"
|
||||
|
||||
DEFAULT_MODEL_SETTINGS: RealtimeSessionModelSettings = {
|
||||
"voice": "ash",
|
||||
@@ -1438,23 +1438,26 @@ class OpenAIRealtimeWebSocketModel(RealtimeModel):
|
||||
or DEFAULT_MODEL_SETTINGS.get("modalities")
|
||||
)
|
||||
|
||||
# Construct full session object. `type` will be excluded at serialization time for updates.
|
||||
session_create_request = OpenAISessionCreateRequest(
|
||||
type="realtime",
|
||||
model=(model_settings.get("model_name") or self.model) or DEFAULT_REALTIME_MODEL,
|
||||
output_modalities=output_modalities,
|
||||
audio=OpenAIRealtimeAudioConfig(
|
||||
session_create_args: dict[str, Any] = {
|
||||
"type": "realtime",
|
||||
"model": (model_settings.get("model_name") or self.model) or DEFAULT_REALTIME_MODEL,
|
||||
"output_modalities": output_modalities,
|
||||
"audio": OpenAIRealtimeAudioConfig(
|
||||
input=OpenAIRealtimeAudioInput(**audio_input_args),
|
||||
output=OpenAIRealtimeAudioOutput(**audio_output_args),
|
||||
),
|
||||
tools=cast(
|
||||
Any,
|
||||
self._tools_to_session_tools(
|
||||
tools=model_settings.get("tools", []),
|
||||
handoffs=model_settings.get("handoffs", []),
|
||||
),
|
||||
"tools": self._tools_to_session_tools(
|
||||
tools=model_settings.get("tools", []),
|
||||
handoffs=model_settings.get("handoffs", []),
|
||||
),
|
||||
)
|
||||
}
|
||||
if model_settings.get("parallel_tool_calls") is not None:
|
||||
session_create_args["parallel_tool_calls"] = model_settings["parallel_tool_calls"]
|
||||
if model_settings.get("reasoning") is not None:
|
||||
session_create_args["reasoning"] = model_settings["reasoning"]
|
||||
|
||||
# Construct full session object. `type` will be excluded at serialization time for updates.
|
||||
session_create_request = OpenAISessionCreateRequest(**session_create_args)
|
||||
|
||||
if "instructions" in model_settings:
|
||||
session_create_request.instructions = model_settings.get("instructions")
|
||||
|
||||
@@ -33,7 +33,7 @@ class TestConversionHelperTryConvertRawMessage:
|
||||
"type": "session.update",
|
||||
"other_data": {
|
||||
"session": {
|
||||
"model": "gpt-realtime-1.5",
|
||||
"model": "gpt-realtime-2",
|
||||
"type": "realtime",
|
||||
"modalities": ["text", "audio"],
|
||||
"voice": "ash",
|
||||
|
||||
@@ -115,8 +115,8 @@ class TestConnectionLifecycle(TestOpenAIRealtimeWebSocketModel):
|
||||
assert model.model == "gpt-4o-realtime-preview"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connect_defaults_to_gpt_realtime_1_5(self, model, mock_websocket):
|
||||
"""Test that connect() uses gpt-realtime-1.5 when no model is provided."""
|
||||
async def test_connect_defaults_to_gpt_realtime_2(self, model, mock_websocket):
|
||||
"""Test that connect() uses gpt-realtime-2 when no model is provided."""
|
||||
config = {
|
||||
"api_key": "test-api-key-123",
|
||||
"initial_model_settings": {},
|
||||
@@ -139,8 +139,8 @@ class TestConnectionLifecycle(TestOpenAIRealtimeWebSocketModel):
|
||||
|
||||
mock_connect.assert_called_once()
|
||||
call_args = mock_connect.call_args
|
||||
assert call_args[0][0] == "wss://api.openai.com/v1/realtime?model=gpt-realtime-1.5"
|
||||
assert model.model == "gpt-realtime-1.5"
|
||||
assert call_args[0][0] == "wss://api.openai.com/v1/realtime?model=gpt-realtime-2"
|
||||
assert model.model == "gpt-realtime-2"
|
||||
|
||||
assert model._websocket_task is not None
|
||||
|
||||
@@ -1488,7 +1488,7 @@ class TestSendEventAndConfig(TestOpenAIRealtimeWebSocketModel):
|
||||
def test_session_config_defaults_audio_formats_when_not_call(self, model):
|
||||
settings: dict[str, Any] = {}
|
||||
cfg = model._get_session_config(settings)
|
||||
assert cfg.model == "gpt-realtime-1.5"
|
||||
assert cfg.model == "gpt-realtime-2"
|
||||
assert cfg.audio is not None
|
||||
assert cfg.audio.input is not None
|
||||
assert cfg.audio.input.format is not None
|
||||
@@ -1497,6 +1497,18 @@ class TestSendEventAndConfig(TestOpenAIRealtimeWebSocketModel):
|
||||
assert cfg.audio.output.format is not None
|
||||
assert cfg.audio.output.format.type == "audio/pcm"
|
||||
|
||||
def test_session_config_includes_reasoning_capable_settings(self, model):
|
||||
settings = {
|
||||
"parallel_tool_calls": False,
|
||||
"reasoning": {"effort": "low"},
|
||||
}
|
||||
cfg = model._get_session_config(settings)
|
||||
payload = cfg.model_dump(exclude_unset=True)
|
||||
|
||||
assert payload["model"] == "gpt-realtime-2"
|
||||
assert payload["parallel_tool_calls"] is False
|
||||
assert payload["reasoning"] == {"effort": "low"}
|
||||
|
||||
def test_session_config_allows_tool_search_as_named_function_tool_choice(self, model):
|
||||
cfg = model._get_session_config(
|
||||
{
|
||||
|
||||
@@ -51,7 +51,7 @@ async def test_build_model_settings_from_agent_merges_agent_fields(monkeypatch:
|
||||
|
||||
monkeypatch.setattr(agent, "get_all_tools", AsyncMock(return_value=[helper]))
|
||||
agent.handoffs = [RealtimeAgent(name="handoff-child")]
|
||||
base_settings: RealtimeSessionModelSettings = {"model_name": "gpt-realtime-1.5"}
|
||||
base_settings: RealtimeSessionModelSettings = {"model_name": "gpt-realtime-2"}
|
||||
starting_settings: RealtimeSessionModelSettings = {"voice": "verse"}
|
||||
run_config: RealtimeRunConfig = {"tracing_disabled": True}
|
||||
|
||||
@@ -68,9 +68,9 @@ async def test_build_model_settings_from_agent_merges_agent_fields(monkeypatch:
|
||||
assert merged["tools"][0].name == helper.name
|
||||
assert merged["handoffs"][0].agent_name == "handoff-child"
|
||||
assert merged["voice"] == "verse"
|
||||
assert merged["model_name"] == "gpt-realtime-1.5"
|
||||
assert merged["model_name"] == "gpt-realtime-2"
|
||||
assert merged["tracing"] is None
|
||||
assert base_settings == {"model_name": "gpt-realtime-1.5"}
|
||||
assert base_settings == {"model_name": "gpt-realtime-2"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -26,10 +26,10 @@ class _DummyModel(pydantic.BaseModel):
|
||||
|
||||
def _session_with_output(fmt: Any | None) -> RealtimeSessionCreateRequest:
|
||||
if fmt is None:
|
||||
return RealtimeSessionCreateRequest(type="realtime", model="gpt-realtime-1.5")
|
||||
return RealtimeSessionCreateRequest(type="realtime", model="gpt-realtime-2")
|
||||
return RealtimeSessionCreateRequest(
|
||||
type="realtime",
|
||||
model="gpt-realtime-1.5",
|
||||
model="gpt-realtime-2",
|
||||
# Use dict for output to avoid importing non-exported symbols in tests
|
||||
audio=RealtimeAudioConfig(output=cast(Any, {"format": fmt})),
|
||||
)
|
||||
@@ -49,7 +49,7 @@ def test_normalize_session_payload_variants() -> None:
|
||||
assert Model._normalize_session_payload(transcription_mapping) is None
|
||||
|
||||
# Valid realtime mapping should be converted to model
|
||||
realtime_mapping: Mapping[str, object] = {"type": "realtime", "model": "gpt-realtime-1.5"}
|
||||
realtime_mapping: Mapping[str, object] = {"type": "realtime", "model": "gpt-realtime-2"}
|
||||
as_model = Model._normalize_session_payload(realtime_mapping)
|
||||
assert isinstance(as_model, RealtimeSessionCreateRequest)
|
||||
assert as_model.type == "realtime"
|
||||
|
||||
@@ -103,7 +103,7 @@ class TestRealtimeTracingIntegration:
|
||||
"session": {
|
||||
"id": "session_456",
|
||||
"type": "realtime",
|
||||
"model": "gpt-realtime-1.5",
|
||||
"model": "gpt-realtime-2",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ class TestRealtimeTracingIntegration:
|
||||
"session": {
|
||||
"id": "session_456",
|
||||
"type": "realtime",
|
||||
"model": "gpt-realtime-1.5",
|
||||
"model": "gpt-realtime-2",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class TestRealtimeTracingIntegration:
|
||||
session_created_event = {
|
||||
"type": "session.created",
|
||||
"event_id": "event_123",
|
||||
"session": {"id": "session_456", "type": "realtime", "model": "gpt-realtime-1.5"},
|
||||
"session": {"id": "session_456", "type": "realtime", "model": "gpt-realtime-2"},
|
||||
}
|
||||
|
||||
with patch.object(model, "send_event") as mock_send_event:
|
||||
@@ -216,7 +216,7 @@ class TestRealtimeTracingIntegration:
|
||||
"session": {
|
||||
"id": "session_456",
|
||||
"type": "realtime",
|
||||
"model": "gpt-realtime-1.5",
|
||||
"model": "gpt-realtime-2",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user