diff --git a/examples/agent_stt/realtime_file/main.py b/examples/agent_stt/realtime_file/main.py index eb910dc2..07988098 100644 --- a/examples/agent_stt/realtime_file/main.py +++ b/examples/agent_stt/realtime_file/main.py @@ -43,6 +43,11 @@ def parse_args() -> argparse.Namespace: "--turn-seconds", type=float, default=5.0, help="fake turn length, --turn-detection external only" ) parser.add_argument("--no-partials", action="store_true") + parser.add_argument( + "--emit-sentences", + action="store_true", + help="one sentence per segment, split on end-of-sentence punctuation: 'Hi. How are you?' arrives as two segments", + ) return parser.parse_args() @@ -75,6 +80,7 @@ def build_client(args: argparse.Namespace, clock: Clock) -> AgentSttAsyncClient: transcription_config = TranscriptionConfig( language=args.language, enable_partials=not args.no_partials, + emit_sentences=args.emit_sentences or None, ) turn_config = TurnConfig(turn_detection_mode=TurnDetectionMode(args.turn_detection)) diff --git a/sdk/agent_stt/README.md b/sdk/agent_stt/README.md index 04b7d538..2e4e9b8a 100644 --- a/sdk/agent_stt/README.md +++ b/sdk/agent_stt/README.md @@ -127,6 +127,14 @@ point, so the transcriber never sees a name it has no notion of. The RT models ( `standard`) are not Agent STT models and are not accepted here; the deprecated `operating_point` still passes through, and suppresses the `model` default so the two never arrive together. +`emit_sentences` splits a segment after each end-of-sentence punctuation mark (such as `.`, `?` +or `!`), so each segment carries one sentence: "Hi there. How are you?" arrives as two segments +rather than one. Off unless you ask for it, and fixed for the life of the session: + +```python +transcription_config = TranscriptionConfig(language="en", emit_sentences=True) +``` + Engine silence-based end of utterance is not offered here. A turn ends either because the service's VAD said so, or because you called `finalize()`. diff --git a/sdk/agent_stt/speechmatics/agent_stt/_models.py b/sdk/agent_stt/speechmatics/agent_stt/_models.py index aa75f855..7de5f3ea 100644 --- a/sdk/agent_stt/speechmatics/agent_stt/_models.py +++ b/sdk/agent_stt/speechmatics/agent_stt/_models.py @@ -182,16 +182,22 @@ class TranscriptionConfig(RTTranscriptionConfig): Attributes: additional_vocab: Words to bias the engine towards, as `AdditionalVocabEntry` objects or raw dicts. - model: Agent STT model, defaulting to `DEFAULT_MODEL`. The proxy in front of the - service resolves the name to the engine's operating point, so a model the - transcriber has no notion of still routes correctly. + model: Agent STT model to transcribe with. Defaults to `DEFAULT_MODEL`. + emit_sentences: Split a segment after each end-of-sentence punctuation mark (such as + `.`, `?` or `!`), so each segment carries a single sentence. For example, + "Hi there. How are you?" arrives as two segments rather than one. Fixed for the + life of the session. Defaults to False service-side. Examples: >>> transcription_config = TranscriptionConfig(language="en", enable_partials=True) + + One sentence per segment: + >>> transcription_config = TranscriptionConfig(language="en", emit_sentences=True) """ model: Model = _UNSET additional_vocab: Optional[list[Union[AdditionalVocabEntry, dict[str, Any]]]] = None + emit_sentences: Optional[bool] = None def __post_init__(self) -> None: if self.model is not _UNSET and self.operating_point is not None: # type: ignore[unreachable] diff --git a/tests/agent_stt/test_client.py b/tests/agent_stt/test_client.py index 043aeb2a..3db89463 100644 --- a/tests/agent_stt/test_client.py +++ b/tests/agent_stt/test_client.py @@ -268,6 +268,31 @@ async def test_start_session_turn_config_wins_over_the_constructor(client, monke assert transport.messages[0]["turn_config"] == {"turn_detection_mode": "vad"} +@pytest.mark.asyncio +async def test_emit_sentences_reaches_start_recognition(client, monkeypatch): + """The whole config dict is sent, but assert it rather than trust it: the flag is the session.""" + transport = StubTransport() + client._transport = transport + monkeypatch.setattr(client, "_ws_connect", _noop) + monkeypatch.setattr(client, "_wait_recognition_started", _noop) + + await client.start_session(transcription_config=TranscriptionConfig(language="en", emit_sentences=True)) + + assert transport.messages[0]["transcription_config"]["emit_sentences"] is True + + +@pytest.mark.asyncio +async def test_emit_sentences_absent_when_not_asked_for(client, monkeypatch): + transport = StubTransport() + client._transport = transport + monkeypatch.setattr(client, "_ws_connect", _noop) + monkeypatch.setattr(client, "_wait_recognition_started", _noop) + + await client.start_session(transcription_config=TranscriptionConfig(language="en")) + + assert "emit_sentences" not in transport.messages[0]["transcription_config"] + + def test_session_info_tracks_the_request_id_across_sessions(client): """RT mints a fresh request_id per session; session_info must not keep the first one.""" start_session(client) diff --git a/tests/agent_stt/test_config.py b/tests/agent_stt/test_config.py index e2938adb..b2aa73a4 100644 --- a/tests/agent_stt/test_config.py +++ b/tests/agent_stt/test_config.py @@ -11,9 +11,7 @@ def test_service_vad_is_the_default(): def test_external_mode_selects_external_turn_detection(): - assert TurnConfig(turn_detection_mode=TurnDetectionMode.EXTERNAL).to_dict() == { - "turn_detection_mode": "external" - } + assert TurnConfig(turn_detection_mode=TurnDetectionMode.EXTERNAL).to_dict() == {"turn_detection_mode": "external"} def test_turn_detection_is_not_in_the_transcription_config(): @@ -48,6 +46,16 @@ def test_rt_fields_still_work(): assert result["max_delay"] == 1.5 +def test_emit_sentences_omitted_by_default(): + """Left out so the service applies its own default of false.""" + assert "emit_sentences" not in TranscriptionConfig(language="en").to_dict() + + +@pytest.mark.parametrize("value", [True, False]) +def test_emit_sentences_sent_when_set(value): + assert TranscriptionConfig(language="en", emit_sentences=value).to_dict()["emit_sentences"] is value + + def test_model_and_operating_point_conflict(): with pytest.raises(ValueError): TranscriptionConfig(model=Model.LINDEN_1, operating_point="enhanced")