Skip to content
Open
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ aai.settings.api_key = "your-key"
- `aai.AsyncTranscript` — What `AsyncTranscriber` returns. Same fields as `Transcript`, coroutine methods
- `aai.AsyncClient` — Shared `httpx.AsyncClient` pool for one or more `AsyncTranscriber`s
- `assemblyai.prerecorded.v2` — Canonical module for both transcribers, matching the `/v2/transcript` API. The top-level `aai.*` names re-export it
- `aai.TranscriptionConfig` — All transcription options: `speech_models`, `speaker_labels`, `sentiment_analysis`, `entity_detection`, `auto_chapters`, `content_safety`, `language_detection`, `summarization`, `word_boost`, `disfluencies`
- `aai.TranscriptionConfig` — All transcription options: `speech_models`, `speaker_labels`, `sentiment_analysis`, `entity_detection`, `auto_chapters`, `content_safety`, `language_detection`, `summarization`, `word_boost`, `disfluencies`, `speech_understanding`
- `aai.Transcript` — Result object with `.text`, `.status`, `.utterances`, `.words`, `.chapters`, `.entities`, `.sentiment_analysis`. Methods: `get_sentences()`, `get_paragraphs()`, `export_subtitles_srt()`, `export_subtitles_vtt()`
- `aai.SyncTranscriber` — Synchronous pre-recorded transcription: audio in, transcript out, one request (no polling). Methods: `transcribe()`, `transcribe_live()`, `open_live()`, `transcribe_async()`, `warm()`
- `aai.AsyncSyncTranscriber` — Asyncio counterpart of `SyncTranscriber`. Same input types, config, result, and errors; `transcribe()`, `transcribe_live()` and `warm()` are coroutines; `open_live()` returns an `AsyncLiveSession`. Owns an HTTP pool: use `async with` or `await aclose()`, or pass an `aai.AsyncClient` to share one
Expand Down
2 changes: 2 additions & 0 deletions assemblyai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
SpeakerOptions,
SpeakerType,
SpeechModel,
SpeechUnderstandingEffort,
SpeechUnderstandingFeatureRequests,
SpeechUnderstandingFeatureResponses,
SpeechUnderstandingRequest,
Expand Down Expand Up @@ -175,6 +176,7 @@ def __getattr__(name: str):
"SpeakerOptions",
"SpeakerType",
"SpeechModel",
"SpeechUnderstandingEffort",
"SpeechUnderstandingFeatureRequests",
"SpeechUnderstandingFeatureResponses",
"SpeechUnderstandingRequest",
Expand Down
2 changes: 1 addition & 1 deletion assemblyai/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.6.1"
__version__ = "1.6.2"
25 changes: 25 additions & 0 deletions assemblyai/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,19 @@ class SpeakerType(str, Enum):
"Identify speakers by their name"


class SpeechUnderstandingEffort(str, Enum):
"""
How much processing power to spend on a speech understanding task. 'medium' produces
higher quality results on harder audio at a higher cost.
"""

low = "low"
"Standard processing effort"

medium = "medium"
"Higher quality processing effort for harder audio"


class SpeakerIdentificationRequest(BaseModel):
"""
Speaker identification configuration for speech understanding
Expand All @@ -712,6 +725,9 @@ class SpeakerIdentificationRequest(BaseModel):
speakers: Optional[List[Dict[str, Any]]] = None
"Known speaker definitions with optional descriptions for improved accuracy"

effort: Optional[Union[SpeechUnderstandingEffort, str]] = None
"How much effort to spend on this task ('low' or 'medium')"


class TranslationRequest(BaseModel):
"""
Expand All @@ -727,6 +743,12 @@ class TranslationRequest(BaseModel):
match_original_utterance: Optional[bool] = False
"Whether to match the original utterance structure in translations (default: False)"

force_translation: Optional[bool] = False
"Whether to translate even when the detected source language equals the target language (default: False)"

effort: Optional[Union[SpeechUnderstandingEffort, str]] = None
"How much effort to spend on this task ('low' or 'medium')"


class CustomFormattingRequest(BaseModel):
"""
Expand All @@ -742,6 +764,9 @@ class CustomFormattingRequest(BaseModel):
email: Optional[str] = None
"Custom email format pattern (e.g., 'username@domain.com')"

effort: Optional[Union[SpeechUnderstandingEffort, str]] = None
"How much effort to spend on this task ('low' or 'medium')"


class SpeechUnderstandingFeatureRequests(BaseModel):
"""
Expand Down
186 changes: 186 additions & 0 deletions tests/unit/test_speech_understanding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import assemblyai as aai


def test_speech_understanding_effort_enum():
assert aai.SpeechUnderstandingEffort.low == "low"
assert aai.SpeechUnderstandingEffort.medium == "medium"
assert aai.SpeechUnderstandingEffort.low.value == "low"
assert aai.SpeechUnderstandingEffort.medium.value == "medium"


def test_speaker_identification_request_effort_enum():
req = aai.SpeakerIdentificationRequest(
speaker_type=aai.SpeakerType.name,
effort=aai.SpeechUnderstandingEffort.medium,
)
assert req.speaker_type == aai.SpeakerType.name
assert req.effort == aai.SpeechUnderstandingEffort.medium
data = req.dict(exclude_none=True)
assert data["effort"] == "medium"
assert data["speaker_type"] == "name"


def test_speaker_identification_request_effort_str():
req = aai.SpeakerIdentificationRequest(
speaker_type=aai.SpeakerType.role,
known_values=["Agent", "Customer"],
effort="low",
)
assert req.speaker_type == aai.SpeakerType.role
assert req.known_values == ["Agent", "Customer"]
assert req.effort == "low"
data = req.dict(exclude_none=True)
assert data["effort"] == "low"


def test_speaker_identification_request_effort_omitted():
req = aai.SpeakerIdentificationRequest(
speaker_type=aai.SpeakerType.name,
)
assert req.effort is None
data = req.dict(exclude_none=True)
assert "effort" not in data


def test_translation_request_effort_and_force_translation():
req = aai.TranslationRequest(
target_languages=["es", "fr"],
force_translation=True,
effort=aai.SpeechUnderstandingEffort.medium,
)
assert req.target_languages == ["es", "fr"]
assert req.force_translation is True
assert req.effort == aai.SpeechUnderstandingEffort.medium
assert req.formal is False
assert req.match_original_utterance is False

data = req.dict(exclude_none=True)
assert data["target_languages"] == ["es", "fr"]
assert data["force_translation"] is True
assert data["effort"] == "medium"


def test_translation_request_defaults():
req = aai.TranslationRequest(target_languages=["de"])
assert req.force_translation is False
assert req.effort is None

data = req.dict(exclude_none=True)
assert data["target_languages"] == ["de"]
assert data["force_translation"] is False
assert "effort" not in data


def test_custom_formatting_request_effort():
req = aai.CustomFormattingRequest(
date="mm/dd/yyyy",
phone_number="(xxx)xxx-xxxx",
effort=aai.SpeechUnderstandingEffort.low,
)
assert req.date == "mm/dd/yyyy"
assert req.phone_number == "(xxx)xxx-xxxx"
assert req.effort == aai.SpeechUnderstandingEffort.low

data = req.dict(exclude_none=True)
assert data["date"] == "mm/dd/yyyy"
assert data["phone_number"] == "(xxx)xxx-xxxx"
assert data["effort"] == "low"
assert "email" not in data


def test_speech_understanding_request_nested_effort():
req = aai.SpeechUnderstandingRequest(
request=aai.SpeechUnderstandingFeatureRequests(
speaker_identification=aai.SpeakerIdentificationRequest(
speaker_type=aai.SpeakerType.name,
effort=aai.SpeechUnderstandingEffort.medium,
),
translation=aai.TranslationRequest(
target_languages=["es"],
force_translation=True,
effort=aai.SpeechUnderstandingEffort.low,
),
custom_formatting=aai.CustomFormattingRequest(
date="mm/dd/yyyy",
effort=aai.SpeechUnderstandingEffort.medium,
),
)
)

data = req.dict(exclude_none=True)
assert data["request"]["speaker_identification"]["effort"] == "medium"
assert data["request"]["translation"]["effort"] == "low"
assert data["request"]["translation"]["force_translation"] is True
assert data["request"]["custom_formatting"]["effort"] == "medium"


def test_transcription_config_speech_understanding_dict_with_effort_and_force_translation():
config = aai.TranscriptionConfig()
config.speech_understanding = {
"request": {
"speaker_identification": {
"speaker_type": "name",
"effort": "medium",
},
"translation": {
"target_languages": ["es"],
"force_translation": True,
"effort": "low",
},
"custom_formatting": {
"date": "mm/dd/yyyy",
"effort": "medium",
},
}
}

assert isinstance(config.speech_understanding, aai.SpeechUnderstandingRequest)
features = config.speech_understanding.request
assert features.speaker_identification.speaker_type == aai.SpeakerType.name
assert features.speaker_identification.effort == "medium"
assert features.translation.target_languages == ["es"]
assert features.translation.force_translation is True
assert features.translation.effort == "low"
assert features.custom_formatting.date == "mm/dd/yyyy"
assert features.custom_formatting.effort == "medium"


def test_speech_understanding_response_parses_request_with_effort_and_force_translation():
raw_response = {
"request": {
"speaker_identification": {
"speaker_type": "name",
"effort": "medium",
},
"translation": {
"target_languages": ["es"],
"force_translation": True,
"effort": "low",
},
"custom_formatting": {
"date": "mm/dd/yyyy",
"effort": "medium",
},
},
"response": {
"speaker_identification": {
"status": "success",
"mapping": {"Speaker A": "Alice"},
},
"translation": {
"status": "success",
},
"custom_formatting": {
"status": "success",
"formatted_text": "Call me at (123)456-7890.",
},
},
}

res = aai.SpeechUnderstandingResponse.parse_obj(raw_response)
assert res.request.speaker_identification.effort == "medium"
assert res.request.translation.effort == "low"
assert res.request.translation.force_translation is True
assert res.request.custom_formatting.effort == "medium"
assert res.response.speaker_identification.status == "success"
assert res.response.speaker_identification.mapping == {"Speaker A": "Alice"}
Loading