From 4a0f68ad4694cf19acaf99d337185a1d2bc06785 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 02:23:31 +0100 Subject: [PATCH 01/10] fix(voice): send language and prompt in streamed STT --- src/agents/voice/models/openai_stt.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/agents/voice/models/openai_stt.py b/src/agents/voice/models/openai_stt.py index cf504d8892..a03203bdfd 100644 --- a/src/agents/voice/models/openai_stt.py +++ b/src/agents/voice/models/openai_stt.py @@ -175,6 +175,12 @@ 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: + 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( { @@ -184,7 +190,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, } }, From 5121a8b839f39b942e636c0f0cc977e72e816b89 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 02:23:40 +0100 Subject: [PATCH 02/10] test(voice): cover streamed STT language and prompt --- tests/voice/test_openai_stt_session_config.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/voice/test_openai_stt_session_config.py diff --git a/tests/voice/test_openai_stt_session_config.py b/tests/voice/test_openai_stt_session_config.py new file mode 100644 index 0000000000..a558f5b1ac --- /dev/null +++ b/tests/voice/test_openai_stt_session_config.py @@ -0,0 +1,51 @@ +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_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" + } From 1f66f705a9143fdd8d0c8649db2c81392d9b837a Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 02:44:51 +0100 Subject: [PATCH 03/10] test(voice): cover gpt-live-transcribe languages --- tests/voice/test_openai_stt_session_config.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/voice/test_openai_stt_session_config.py b/tests/voice/test_openai_stt_session_config.py index a558f5b1ac..9929b73dbd 100644 --- a/tests/voice/test_openai_stt_session_config.py +++ b/tests/voice/test_openai_stt_session_config.py @@ -30,6 +30,29 @@ async def test_streaming_stt_sends_language_and_prompt() -> None: } +@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( From 99c7abed3517a92471481e18b1f6ff72fcdd8e4d Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 02:45:27 +0100 Subject: [PATCH 04/10] fix(voice): use plural languages for live transcription --- src/agents/voice/models/openai_stt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agents/voice/models/openai_stt.py b/src/agents/voice/models/openai_stt.py index a03203bdfd..5d098507f6 100644 --- a/src/agents/voice/models/openai_stt.py +++ b/src/agents/voice/models/openai_stt.py @@ -177,7 +177,10 @@ 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: - transcription_config["language"] = self._settings.language + if self._model == "gpt-live-transcribe": + 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 From a24c1c35cc43dad61141732aeb828c5c1a168b66 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:31:28 +0100 Subject: [PATCH 05/10] chore: apply STT review follow-up --- .github/workflows/followup-4533.yml | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/followup-4533.yml diff --git a/.github/workflows/followup-4533.yml b/.github/workflows/followup-4533.yml new file mode 100644 index 0000000000..e21d23b4e4 --- /dev/null +++ b/.github/workflows/followup-4533.yml @@ -0,0 +1,73 @@ +name: Apply STT review follow-up + +on: + push: + branches: + - fix/stt-stream-language-prompt + +permissions: + contents: write + +jobs: + patch: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: fix/stt-stream-language-prompt + fetch-depth: 0 + - name: Apply review changes + shell: bash + run: | + python - <<'PY' + from pathlib import Path + + src = Path('src/agents/voice/models/openai_stt.py') + text = src.read_text() + old = 'if self._model == "gpt-live-transcribe":' + new = 'if self._model in {"gpt-transcribe", "gpt-live-transcribe"}:' + if text.count(old) != 1: + raise SystemExit(f'expected one model condition, found {text.count(old)}') + src.write_text(text.replace(old, new, 1)) + + test = Path('tests/voice/test_openai_stt_session_config.py') + text = test.read_text() + marker = '@pytest.mark.asyncio\nasync def test_streaming_stt_sends_plural_languages_for_gpt_live_transcribe() -> None:\n' + if marker not in text: + raise SystemExit('live-transcribe test marker not found') + addition = '''@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", + } + + +''' + test.write_text(text.replace(marker, addition + marker, 1)) + PY + rm .github/workflows/followup-4533.yml + - name: Commit follow-up + shell: bash + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "fix(voice): map current transcription languages" + git push origin HEAD:fix/stt-stream-language-prompt From 319f90997bdc3cb7beab7639032ded191443f7fd Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:34:10 +0100 Subject: [PATCH 06/10] chore: remove temporary follow-up workflow --- .github/workflows/followup-4533.yml | 73 ----------------------------- 1 file changed, 73 deletions(-) delete mode 100644 .github/workflows/followup-4533.yml diff --git a/.github/workflows/followup-4533.yml b/.github/workflows/followup-4533.yml deleted file mode 100644 index e21d23b4e4..0000000000 --- a/.github/workflows/followup-4533.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Apply STT review follow-up - -on: - push: - branches: - - fix/stt-stream-language-prompt - -permissions: - contents: write - -jobs: - patch: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: fix/stt-stream-language-prompt - fetch-depth: 0 - - name: Apply review changes - shell: bash - run: | - python - <<'PY' - from pathlib import Path - - src = Path('src/agents/voice/models/openai_stt.py') - text = src.read_text() - old = 'if self._model == "gpt-live-transcribe":' - new = 'if self._model in {"gpt-transcribe", "gpt-live-transcribe"}:' - if text.count(old) != 1: - raise SystemExit(f'expected one model condition, found {text.count(old)}') - src.write_text(text.replace(old, new, 1)) - - test = Path('tests/voice/test_openai_stt_session_config.py') - text = test.read_text() - marker = '@pytest.mark.asyncio\nasync def test_streaming_stt_sends_plural_languages_for_gpt_live_transcribe() -> None:\n' - if marker not in text: - raise SystemExit('live-transcribe test marker not found') - addition = '''@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", - } - - -''' - test.write_text(text.replace(marker, addition + marker, 1)) - PY - rm .github/workflows/followup-4533.yml - - name: Commit follow-up - shell: bash - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "fix(voice): map current transcription languages" - git push origin HEAD:fix/stt-stream-language-prompt From f561b5ade5ec1acc575aca36e09117be54b44347 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:34:58 +0100 Subject: [PATCH 07/10] fix(voice): map current transcription languages --- src/agents/voice/models/openai_stt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/voice/models/openai_stt.py b/src/agents/voice/models/openai_stt.py index 5d098507f6..a1d95746ea 100644 --- a/src/agents/voice/models/openai_stt.py +++ b/src/agents/voice/models/openai_stt.py @@ -177,7 +177,7 @@ 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 == "gpt-live-transcribe": + if self._model in {"gpt-transcribe", "gpt-live-transcribe"}: transcription_config["languages"] = [self._settings.language] else: transcription_config["language"] = self._settings.language From 2f485f1553890270994c04f413b18307bb7e0394 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:35:16 +0100 Subject: [PATCH 08/10] test(voice): cover gpt-transcribe languages --- tests/voice/test_openai_stt_session_config.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/voice/test_openai_stt_session_config.py b/tests/voice/test_openai_stt_session_config.py index 9929b73dbd..97b37dee59 100644 --- a/tests/voice/test_openai_stt_session_config.py +++ b/tests/voice/test_openai_stt_session_config.py @@ -30,6 +30,29 @@ async def test_streaming_stt_sends_language_and_prompt() -> None: } +@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( From c12d29653da317b6c43281a1133fbe499955ff70 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:55:06 +0100 Subject: [PATCH 09/10] fix(voice): keep gpt-transcribe language singular --- src/agents/voice/models/openai_stt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/voice/models/openai_stt.py b/src/agents/voice/models/openai_stt.py index a1d95746ea..5d098507f6 100644 --- a/src/agents/voice/models/openai_stt.py +++ b/src/agents/voice/models/openai_stt.py @@ -177,7 +177,7 @@ 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"}: + if self._model == "gpt-live-transcribe": transcription_config["languages"] = [self._settings.language] else: transcription_config["language"] = self._settings.language From a594020586752c07dacf4632cdba838f062684af Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 21 Aug 2026 23:55:15 +0100 Subject: [PATCH 10/10] test(voice): cover gpt-transcribe singular language --- tests/voice/test_openai_stt_session_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/voice/test_openai_stt_session_config.py b/tests/voice/test_openai_stt_session_config.py index 97b37dee59..2de1114f0a 100644 --- a/tests/voice/test_openai_stt_session_config.py +++ b/tests/voice/test_openai_stt_session_config.py @@ -31,7 +31,7 @@ async def test_streaming_stt_sends_language_and_prompt() -> None: @pytest.mark.asyncio -async def test_streaming_stt_sends_plural_languages_for_gpt_transcribe() -> None: +async def test_streaming_stt_sends_singular_language_for_gpt_transcribe() -> None: session = OpenAISTTTranscriptionSession( input=StreamedAudioInput(), client=AsyncMock(api_key="FAKE_KEY"), @@ -48,7 +48,7 @@ async def test_streaming_stt_sends_plural_languages_for_gpt_transcribe() -> None payload = json.loads(websocket.send.await_args.args[0]) assert payload["session"]["audio"]["input"]["transcription"] == { "model": "gpt-transcribe", - "languages": ["fr"], + "language": "fr", "prompt": "domain vocabulary", }