Repository: livekit/agents
Pull request #1861: fix azure language
Author: login: jayeshp19
Created: 2025-04-02T11:35:02Z | Merged: 2025-04-03T10:02:30Z

fixes: https://github.com/livekit/agents/issues/1858


Diff:
diff --git a/.github/next-release/changeset-56dd097d.md b/.github/next-release/changeset-56dd097d.md
new file mode 100644
index 0000000000..1337194fc7
--- /dev/null
+++ b/.github/next-release/changeset-56dd097d.md
@@ -0,0 +1,5 @@
+---
+"livekit-plugins-azure": patch
+---
+
+fix: remove `languages` parameter from constructor, retain only `language` (#1861)
\ No newline at end of file
diff --git a/livekit-plugins/livekit-plugins-azure/livekit/plugins/azure/stt.py b/livekit-plugins/livekit-plugins-azure/livekit/plugins/azure/stt.py
index 44104acc9b..1bff02a94b 100644
--- a/livekit-plugins/livekit-plugins-azure/livekit/plugins/azure/stt.py
+++ b/livekit-plugins/livekit-plugins-azure/livekit/plugins/azure/stt.py
@@ -42,7 +42,7 @@ class STTOptions:
     segmentation_silence_timeout_ms: NotGivenOr[int]
     segmentation_max_time_ms: NotGivenOr[int]
     segmentation_strategy: NotGivenOr[str]
-    languages: list[
+    language: list[
         str
     ]  # see https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=stt
     speech_endpoint: NotGivenOr[str] = NOT_GIVEN
@@ -63,9 +63,7 @@ def __init__(
         segmentation_max_time_ms: NotGivenOr[int] = NOT_GIVEN,
         segmentation_strategy: NotGivenOr[str] = NOT_GIVEN,
         # Azure handles multiple languages and can auto-detect the language used. It requires the candidate set to be set.  # noqa: E501
-        languages: NotGivenOr[list[str]] = NOT_GIVEN,
-        # for compatibility with other STT plugins
-        language: NotGivenOr[str] = NOT_GIVEN,
+        language: NotGivenOr[str | list[str] | None] = NOT_GIVEN,
         profanity: NotGivenOr[speechsdk.enums.ProfanityOption] = NOT_GIVEN,
     ):
         """
@@ -79,12 +77,11 @@ def __init__(
         """
 
         super().__init__(capabilities=stt.STTCapabilities(streaming=True, interim_results=True))
+        if not language or not is_given(language):
+            language = ["en-US"]
 
-        if not is_given(languages):
-            languages = ["en-US"]
-
-        if is_given(language) and not is_given(languages):
-            languages = [language]
+        if isinstance(language, str):
+            language = [language]
 
         if not is_given(speech_host):
             speech_host = os.environ.get("AZURE_SPEECH_HOST")
@@ -109,7 +106,7 @@ def __init__(
             speech_region=speech_region,
             speech_host=speech_host,
             speech_auth_token=speech_auth_token,
-            languages=languages,
+            language=language,
             sample_rate=sample_rate,
             num_channels=num_channels,
             segmentation_silence_timeout_ms=segmentation_silence_timeout_ms,
@@ -131,28 +128,23 @@ async def _recognize_impl(
     def stream(
         self,
         *,
-        languages: NotGivenOr[list[str]] = NOT_GIVEN,
         language: NotGivenOr[str] = NOT_GIVEN,
         conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
     ) -> SpeechStream:
         config = deepcopy(self._config)
-        if is_given(language) and not is_given(languages):
-            languages = [language]
-        if is_given(languages):
-            config.languages = languages
+        if is_given(language):
+            config.language = [language]
         stream = SpeechStream(stt=self, opts=config, conn_options=conn_options)
         self._streams.add(stream)
         return stream
 
-    def update_options(
-        self, *, language: NotGivenOr[str] = NOT_GIVEN, languages: NotGivenOr[list[str]] = NOT_GIVEN
-    ):
-        if is_given(language) and not is_given(languages):
-            languages = [language]
-        if is_given(languages):
-            self._config.languages = languages
+    def update_options(self, *, language: NotGivenOr[list[str] | str] = NOT_GIVEN):
+        if is_given(language):
+            if isinstance(language, str):
+                language = [language]
+            self._config.language = language
             for stream in self._streams:
-                stream.update_options(languages=languages)
+                stream.update_options(language=language)
 
 
 class SpeechStream(stt.SpeechStream):
@@ -167,8 +159,8 @@ def __init__(self, *, stt: STT, opts: STTOptions, conn_options: APIConnectOption
         self._loop = asyncio.get_running_loop()
         self._reconnect_event = asyncio.Event()
 
-    def update_options(self, *, languages: list[str]):
-        self._opts.languages = languages
+    def update_options(self, *, language: list[str]):
+        self._opts.language = language
         self._reconnect_event.set()
 
     async def _run(self) -> None:
@@ -232,8 +224,8 @@ def _on_recognized(self, evt: speechsdk.SpeechRecognitionEventArgs):
         if not text:
             return
 
-        if not detected_lg and self._opts.languages:
-            detected_lg = self._opts.languages[0]
+        if not detected_lg and self._opts.language:
+            detected_lg = self._opts.language[0]
 
         final_data = stt.SpeechData(language=detected_lg, confidence=1.0, text=evt.result.text)
 
@@ -251,8 +243,8 @@ def _on_recognizing(self, evt: speechsdk.SpeechRecognitionEventArgs):
         if not text:
             return
 
-        if not detected_lg and self._opts.languages:
-            detected_lg = self._opts.languages[0]
+        if not detected_lg and self._opts.language:
+            detected_lg = self._opts.language[0]
 
         interim_data = stt.SpeechData(language=detected_lg, confidence=0.0, text=evt.result.text)
 
@@ -331,9 +323,9 @@ def _create_speech_recognizer(
         speech_config.set_profanity(config.profanity)
 
     auto_detect_source_language_config = None
-    if config.languages and len(config.languages) >= 1:
+    if config.language and len(config.language) >= 1:
         auto_detect_source_language_config = (
-            speechsdk.languageconfig.AutoDetectSourceLanguageConfig(languages=config.languages)
+            speechsdk.languageconfig.AutoDetectSourceLanguageConfig(languages=config.language)
         )
 
     audio_config = speechsdk.audio.AudioConfig(stream=stream)


Reviews:
user: login: theomonnom
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-03T09:50:01Z
user: login: theomonnom
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-03T09:50:17Z
user: login: jayeshp19
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T09:52:53Z

Review comments:
diff_hunk: @@ -42,7 +42,7 @@ class STTOptions:
     segmentation_silence_timeout_ms: NotGivenOr[int]
     segmentation_max_time_ms: NotGivenOr[int]
     segmentation_strategy: NotGivenOr[str]
-    languages: list[
path: livekit-plugins/livekit-plugins-azure/livekit/plugins/azure/stt.py
user: login: theomonnom
body: make sure language also accept str
created_at: 2025-04-03T09:50:17Z
author_association: MEMBER
diff_hunk: @@ -42,7 +42,7 @@ class STTOptions:
     segmentation_silence_timeout_ms: NotGivenOr[int]
     segmentation_max_time_ms: NotGivenOr[int]
     segmentation_strategy: NotGivenOr[str]
-    languages: list[
path: livekit-plugins/livekit-plugins-azure/livekit/plugins/azure/stt.py
user: login: jayeshp19
body: It does, we're just converting to list before storing into options
created_at: 2025-04-03T09:52:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-02T11:35:18Z
body: <!-- changeset-checker -->
### :x: Invalid Changeset Format Detected

One or more changeset files in this PR have an invalid format. Please ensure they adhere to:

- Start with `---` and include a closing `---` on its own line.
- Each package line must be in the format:
  `"package-name": patch|minor|major`
- No duplicate package entries allowed.
- A non-empty change description must follow the front matter.

**Error details:**
.github/next-release/changeset-748e3047.md: Failed to read file from git branch 'pr_head'.
.github/next-release/changeset-85f28695.md: Failed to read file from git branch 'pr_head'.
.github/next-release/changeset-e9145707.md: Failed to read file from git branch 'pr_head'.
author_association: CONTRIBUTOR
user: login: theomonnom
created_at: 2025-04-02T12:26:30Z
body: why do we have two language fields lol?
author_association: MEMBER
user: login: jayeshp19
created_at: 2025-04-02T13:27:13Z
body: > why do we have two language fields lol?

It was there because other plugins use `language` but you're right, I've updated to only use `languages`
author_association: CONTRIBUTOR
user: login: theomonnom
created_at: 2025-04-02T17:32:40Z
body: let's keep the language field, without s
author_association: MEMBER