Skip to content
Draft
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: 2 additions & 0 deletions assemblyai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
KeytermsPromptOptions,
LanguageCode,
LanguageDetectionOptions,
LanguageHints,
ListTranscriptParameters,
ListTranscriptResponse,
PageDetails,
Expand Down Expand Up @@ -117,6 +118,7 @@ def __getattr__(name: str):
"KeytermsPromptOptions",
"LanguageCode",
"LanguageDetectionOptions",
"LanguageHints",
"ListTranscriptParameters",
"ListTranscriptResponse",
"PageDetails",
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.3.0"
__version__ = "1.3.1"
84 changes: 84 additions & 0 deletions assemblyai/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,64 @@ def validate_max_speakers(cls, v, values):
return v


class LanguageHints(BaseModel):
"""Optional system-prompt conditioning, independent of language routing.

The default is a canonical language code. When supplied, languages is an
ordered, unique list; the default may be outside it. The server validates supported
codes; omitting this object leaves the system prompt unconditioned.
"""

default_language: str
languages: Optional[List[str]] = None

if pydantic_v2:
model_config = ConfigDict(extra="forbid")

@field_validator("default_language")
@classmethod
def validate_default(cls, value):
return cls._code(value)

@field_validator("languages")
@classmethod
def validate_languages(cls, value):
return cls._languages(value)

else:

class Config:
extra = "forbid"

@validator("default_language")
def validate_default(cls, value):
return cls._code(value)

@validator("languages")
def validate_languages(cls, value):
return cls._languages(value)

@staticmethod
def _code(value):
code = value.strip().lower()
if not code.isascii() or not code.isalpha() or len(code) not in (2, 3):
raise ValueError("language hints require canonical language codes")
return code

@classmethod
def _languages(cls, value):
if value is None:
return None
codes = [cls._code(code) for code in value]
if not codes or len(set(codes)) != len(codes):
raise ValueError("languages must be a nonempty list of unique codes")
return codes


class RawTranscriptionConfig(BaseModel):
language_hints: Optional[LanguageHints] = None
"Optional system-prompt language default and ordered language list."

language_code: Optional[Union[str, LanguageCode]] = None
"""
The language of your audio file. Possible values are found in Supported Languages.
Expand Down Expand Up @@ -1079,11 +1136,13 @@ def __init__(
keyterms_prompt_options: Optional[KeytermsPromptOptions] = None,
speech_understanding: Optional[SpeechUnderstandingRequest] = None,
domain: Optional[str] = None,
language_hints: Optional[Union[LanguageHints, Dict[str, Any]]] = None,
) -> None:
"""
Args:
language_code: The language of your audio file. Possible values are found in Supported Languages.
language_codes: A list of language codes for multilingual/code-switching audio.
language_hints: Optional system-prompt conditioning, independent of routing.
punctuate: Enable Automatic Punctuation
format_text: Enable Text Formatting
dual_channel: Enable Dual Channel transcription
Expand Down Expand Up @@ -1189,6 +1248,12 @@ def __init__(
self.keyterms_prompt_options = keyterms_prompt_options
self.speech_understanding = speech_understanding
self.domain = domain
if language_hints is not None or raw_transcription_config is None:
self.language_hints = (
LanguageHints(**language_hints)
if isinstance(language_hints, dict)
else language_hints
)

@property
def raw(self) -> RawTranscriptionConfig:
Expand Down Expand Up @@ -1237,6 +1302,22 @@ def prompt(self, prompt: Optional[str]) -> None:
"Sets the prompt to use for the transcription."
self._raw_transcription_config.prompt = prompt

@property
def language_hints(self) -> Optional[LanguageHints]:
"""The system-prompt language default and optional ordered language list."""
return self._raw_transcription_config.language_hints

@language_hints.setter
def language_hints(
self, value: Optional[Union[LanguageHints, Dict[str, Any]]]
) -> None:
if value is None:
self._raw_transcription_config.language_hints = None
elif isinstance(value, LanguageHints):
self._raw_transcription_config.language_hints = value.copy(deep=True)
else:
self._raw_transcription_config.language_hints = LanguageHints(**value)

@property
def temperature(self) -> Optional[float]:
"The temperature to use for the transcription."
Expand Down Expand Up @@ -2295,6 +2376,9 @@ class BaseTranscript(BaseModel):
Available transcription features
"""

language_hints: Optional[LanguageHints] = None
"Optional system-prompt language default and ordered language list."

language_code: Optional[Union[str, LanguageCode]] = None
"""
The language of your audio file. Possible values are found in Supported Languages.
Expand Down
101 changes: 101 additions & 0 deletions tests/unit/test_language_hints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import copy
import json

import httpx
import pytest

import assemblyai as aai
from assemblyai.api import ENDPOINT_TRANSCRIPT
from tests.unit import factories

aai.settings.api_key = "test"


@pytest.mark.parametrize(
"hint",
[
None,
{"default_language": "en"},
{"default_language": "en", "languages": ["de", "fr", "en"]},
{"default_language": "en", "languages": ["fr"]},
{"default_language": "de", "languages": ["hi", "en"]},
],
)
def test_hint_survives_actual_submit_request(httpx_mock, hint):
response = factories.generate_dict_factory(
factories.TranscriptProcessingResponseFactory
)()
httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_TRANSCRIPT}",
method="POST",
status_code=httpx.codes.OK,
json=response,
)
config = aai.TranscriptionConfig(
language_hints=hint,
prompt="Transcribe exactly.",
language_detection=True,
language_codes=["fr", "en"],
)
aai.Transcriber().submit("https://example.org/audio.wav", config=config)
payload = json.loads(httpx_mock.get_request().content)
if hint is None:
assert "language_hints" not in payload
else:
assert payload["language_hints"] == hint
assert payload["prompt"] == "Transcribe exactly."
assert payload["language_detection"] is True
assert payload["language_codes"] == ["fr", "en"]


def test_per_file_config_copies_and_clear():
shared = aai.TranscriptionConfig(
language_hints={"default_language": "en", "languages": ["en", "fr"]}
)
french = copy.deepcopy(shared)
french.language_hints = {"default_language": "fr", "languages": ["fr", "en"]}
assert shared.language_hints.default_language == "en"
assert shared.language_hints.languages == ["en", "fr"]
french.language_hints = None
assert french.language_hints is None
model = aai.LanguageHints(default_language="en", languages=["de", "en"])
shared.language_hints = model
model.languages.append("fr")
assert shared.language_hints.languages == ["de", "en"]


@pytest.mark.parametrize(
"hint",
[
{},
{"default_language": ""},
{"default_language": "English"},
{"default_language": "en", "languages": []},
{"default_language": "en", "languages": ["en", "EN"]},
{"default_language": "en", "unknown": "value"},
],
)
def test_invalid_hint_rejected(hint):
with pytest.raises(ValueError):
aai.TranscriptionConfig(language_hints=hint)


def test_raw_configuration_preserves_hints():
raw = aai.RawTranscriptionConfig(
language_hints={"default_language": "fr", "languages": ["en", "fr"]}
)
config = aai.TranscriptionConfig(raw_transcription_config=raw)
assert config.language_hints.default_language == "fr"
assert config.language_hints.languages == ["en", "fr"]
override = aai.TranscriptionConfig(
raw_transcription_config=raw, language_hints={"default_language": "de"}
)
assert override.language_hints.default_language == "de"


def test_constructor_copies_typed_hint():
hint = aai.LanguageHints(default_language="en", languages=["fr"])
config = aai.TranscriptionConfig(language_hints=hint)
hint.languages.append("de")
assert config.language_hints.default_language == "en"
assert config.language_hints.languages == ["fr"]
Loading