Skip to content
11 changes: 10 additions & 1 deletion src/agents/voice/models/openai_stt.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ async def _event_listener(self) -> None:

async def _configure_session(self) -> None:
assert self._websocket is not None, "Websocket not initialized"
transcription_config: dict[str, Any] = {"model": self._model}
if self._settings.language is not None:
if self._model in {"gpt-transcribe", "gpt-live-transcribe"}:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep gpt-transcribe on the singular language field

When callers select gpt-transcribe and set STTModelSettings.language, this condition now serializes the value as languages, but docs/realtime/guide.md:115 reserves plural languages for gpt-live-transcribe, while line 140 identifies gpt-transcribe's plural field as completion output rather than expected-language input. The session update therefore does not send the requested singular input language and may be rejected; keep gpt-transcribe on language and reserve this list conversion for gpt-live-transcribe. Fresh evidence since the prior thread is the follow-up widening of this condition to include gpt-transcribe.

AGENTS.md reference: AGENTS.md:L167-L167

Useful? React with 👍 / 👎.

transcription_config["languages"] = [self._settings.language]
else:
transcription_config["language"] = self._settings.language
if self._settings.prompt is not None:
transcription_config["prompt"] = self._settings.prompt

await self._websocket.send(
json.dumps(
{
Expand All @@ -184,7 +193,7 @@ async def _configure_session(self) -> None:
"audio": {
"input": {
"format": {"type": "audio/pcm", "rate": 24000},
"transcription": {"model": self._model},
"transcription": transcription_config,
"turn_detection": self._turn_detection,
}
},
Expand Down
97 changes: 97 additions & 0 deletions tests/voice/test_openai_stt_session_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import json
from unittest.mock import AsyncMock

import pytest

from agents.voice import StreamedAudioInput, STTModelSettings
from agents.voice.models.openai_stt import OpenAISTTTranscriptionSession


@pytest.mark.asyncio
async def test_streaming_stt_sends_language_and_prompt() -> None:
session = OpenAISTTTranscriptionSession(
input=StreamedAudioInput(),
client=AsyncMock(api_key="FAKE_KEY"),
model="gpt-4o-transcribe",
settings=STTModelSettings(language="fr", prompt="domain vocabulary"),
trace_include_sensitive_data=False,
trace_include_sensitive_audio_data=False,
)
websocket = AsyncMock()
session._websocket = websocket

await session._configure_session()

payload = json.loads(websocket.send.await_args.args[0])
assert payload["session"]["audio"]["input"]["transcription"] == {
"model": "gpt-4o-transcribe",
"language": "fr",
"prompt": "domain vocabulary",
}


@pytest.mark.asyncio
async def test_streaming_stt_sends_plural_languages_for_gpt_transcribe() -> None:
session = OpenAISTTTranscriptionSession(
input=StreamedAudioInput(),
client=AsyncMock(api_key="FAKE_KEY"),
model="gpt-transcribe",
settings=STTModelSettings(language="fr", prompt="domain vocabulary"),
trace_include_sensitive_data=False,
trace_include_sensitive_audio_data=False,
)
websocket = AsyncMock()
session._websocket = websocket

await session._configure_session()

payload = json.loads(websocket.send.await_args.args[0])
assert payload["session"]["audio"]["input"]["transcription"] == {
"model": "gpt-transcribe",
"languages": ["fr"],
"prompt": "domain vocabulary",
}


@pytest.mark.asyncio
async def test_streaming_stt_sends_plural_languages_for_gpt_live_transcribe() -> None:
session = OpenAISTTTranscriptionSession(
input=StreamedAudioInput(),
client=AsyncMock(api_key="FAKE_KEY"),
model="gpt-live-transcribe",
settings=STTModelSettings(language="fr", prompt="domain vocabulary"),
trace_include_sensitive_data=False,
trace_include_sensitive_audio_data=False,
)
websocket = AsyncMock()
session._websocket = websocket

await session._configure_session()

payload = json.loads(websocket.send.await_args.args[0])
assert payload["session"]["audio"]["input"]["transcription"] == {
"model": "gpt-live-transcribe",
"languages": ["fr"],
"prompt": "domain vocabulary",
}


@pytest.mark.asyncio
async def test_streaming_stt_omits_unset_language_and_prompt() -> None:
session = OpenAISTTTranscriptionSession(
input=StreamedAudioInput(),
client=AsyncMock(api_key="FAKE_KEY"),
model="gpt-4o-transcribe",
settings=STTModelSettings(),
trace_include_sensitive_data=False,
trace_include_sensitive_audio_data=False,
)
websocket = AsyncMock()
session._websocket = websocket

await session._configure_session()

payload = json.loads(websocket.send.await_args.args[0])
assert payload["session"]["audio"]["input"]["transcription"] == {
"model": "gpt-4o-transcribe"
}
Loading