Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/agent_stt/realtime_file/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()


Expand Down Expand Up @@ -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))

Expand Down
8 changes: 8 additions & 0 deletions sdk/agent_stt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.

Expand Down
12 changes: 9 additions & 3 deletions sdk/agent_stt/speechmatics/agent_stt/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
25 changes: 25 additions & 0 deletions tests/agent_stt/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 11 additions & 3 deletions tests/agent_stt/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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")
Expand Down
Loading