33
44import logging
55import time
6+ import warnings
7+ from collections .abc import Awaitable , Callable
68from typing import TYPE_CHECKING , Any , Optional
79
810if TYPE_CHECKING :
911 import azure .cognitiveservices .speech as speechsdk # noqa: F401
1012
11- from pyrit .auth .azure_auth import get_speech_config
13+ from pyrit .auth .azure_auth import get_speech_config , get_speech_config_async
1214from pyrit .common import default_values
1315from pyrit .identifiers import ComponentIdentifier
1416from pyrit .models import PromptDataType , data_serializer_factory
@@ -21,6 +23,14 @@ class AzureSpeechAudioToTextConverter(PromptConverter):
2123 """
2224 Transcribes a .wav audio file into text using Azure AI Speech service.
2325
26+ Authentication is auto-detected from the provided credentials, in priority order:
27+
28+ 1. If ``azure_speech_key`` is a **callable** token provider, it takes highest priority — it is
29+ resolved at conversion time and used with Entra ID auth (``azure_speech_resource_id`` required).
30+ 2. If ``azure_speech_key`` is a **string** (or the ``AZURE_SPEECH_KEY`` env var is set), API key auth is used.
31+ 3. If **neither** is provided, Entra ID auth is used automatically via ``DefaultAzureCredential``
32+ and ``azure_speech_resource_id`` must be set.
33+
2434 https://learn.microsoft.com/en-us/azure/ai-services/speech-service/speech-to-text
2535 """
2636
@@ -36,52 +46,75 @@ class AzureSpeechAudioToTextConverter(PromptConverter):
3646
3747 def __init__ (
3848 self ,
49+ * ,
3950 azure_speech_region : Optional [str ] = None ,
40- azure_speech_key : Optional [str ] = None ,
51+ azure_speech_key : Optional [str | Callable [[], str | Awaitable [ str ]] ] = None ,
4152 azure_speech_resource_id : Optional [str ] = None ,
42- use_entra_auth : bool = False ,
53+ use_entra_auth : Optional [ bool ] = None ,
4354 recognition_language : str = "en-US" ,
4455 ) -> None :
4556 """
4657 Initialize the converter with Azure Speech service credentials and recognition language.
4758
4859 Args:
4960 azure_speech_region (str, Optional): The name of the Azure region.
50- azure_speech_key (str, Optional): The API key for accessing the service (if not using Entra ID auth).
61+ azure_speech_key (str | Callable[[], str | Awaitable[str]], Optional): The API key for accessing
62+ the service, or a sync/async callable that returns a token string.
63+ If a string key is provided (or the ``AZURE_SPEECH_KEY`` env var is set), key auth is used.
64+ If a callable token provider is provided, it is resolved at conversion time and used with
65+ Entra ID auth (``azure_speech_resource_id`` must also be set).
66+ If omitted, Entra ID auth via ``DefaultAzureCredential`` is used automatically.
5167 azure_speech_resource_id (str, Optional): The resource ID for accessing the service when using
52- Entra ID auth. This can be found by selecting 'Properties' in the 'Resource Management'
53- section of your Azure Speech resource in the Azure portal.
54- use_entra_auth (bool): Whether to use Entra ID authentication. If True, azure_speech_resource_id
55- must be provided. If False, azure_speech_key must be provided. Defaults to False.
68+ Entra ID auth. Required when using a callable token provider or when no API key is available.
69+ use_entra_auth (bool, Optional): **Deprecated.** Will be removed in v0.15.0.
70+ Authentication is now auto-detected from the provided credentials.
5671 recognition_language (str): Recognition voice language. Defaults to "en-US".
5772 For more on supported languages, see the following link:
5873 https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support
5974
6075 Raises:
61- ValueError: If the required environment variables are not set, if azure_speech_key is passed in
62- when use_entra_auth is True, or if azure_speech_resource_id is passed in when use_entra_auth
63- is False.
76+ ValueError: If the required environment variables or parameters are not set.
6477 """
78+ if use_entra_auth is not None :
79+ warnings .warn (
80+ "'use_entra_auth' is deprecated and will be removed in v0.15.0. "
81+ "Authentication is now auto-detected: pass a key string for key auth, "
82+ "a callable token provider for token auth, or omit for automatic Entra ID auth." ,
83+ DeprecationWarning ,
84+ stacklevel = 2 ,
85+ )
86+
6587 self ._azure_speech_region : str = default_values .get_required_value (
6688 env_var_name = self .AZURE_SPEECH_REGION_ENVIRONMENT_VARIABLE ,
6789 passed_value = azure_speech_region ,
6890 )
69- if use_entra_auth :
70- if azure_speech_key :
71- raise ValueError ("If using Entra ID auth, please do not specify azure_speech_key." )
91+
92+ self ._token_provider : Callable [[], str | Awaitable [str ]] | None = None
93+ self ._azure_speech_key : str | None = None
94+ self ._azure_speech_resource_id : str | None = None
95+
96+ if azure_speech_key is not None and callable (azure_speech_key ):
97+ self ._token_provider = azure_speech_key
7298 self ._azure_speech_resource_id = default_values .get_required_value (
7399 env_var_name = self .AZURE_SPEECH_RESOURCE_ID_ENVIRONMENT_VARIABLE ,
74100 passed_value = azure_speech_resource_id ,
75101 )
76- self ._azure_speech_key = None
77102 else :
78- if azure_speech_resource_id :
79- raise ValueError ("If using key auth, please do not specify azure_speech_resource_id." )
80- self ._azure_speech_key = default_values .get_required_value (
103+ key_value = default_values .get_non_required_value (
81104 env_var_name = self .AZURE_SPEECH_KEY_ENVIRONMENT_VARIABLE ,
82105 passed_value = azure_speech_key ,
83106 )
84- self ._azure_speech_resource_id = None
107+ if key_value :
108+ self ._azure_speech_key = key_value
109+ else :
110+ logger .info (
111+ "No azure_speech_key provided. "
112+ "Entra ID authentication will be attempted via DefaultAzureCredential."
113+ )
114+ self ._azure_speech_resource_id = default_values .get_required_value (
115+ env_var_name = self .AZURE_SPEECH_RESOURCE_ID_ENVIRONMENT_VARIABLE ,
116+ passed_value = azure_speech_resource_id ,
117+ )
85118
86119 self ._recognition_language = recognition_language
87120 # Create a flag to indicate when recognition is finished
@@ -126,7 +159,13 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "audi
126159 audio_bytes = await audio_serializer .read_data ()
127160
128161 try :
129- transcript = self .recognize_audio (audio_bytes )
162+ speech_config = await get_speech_config_async (
163+ token_provider = self ._token_provider ,
164+ resource_id = self ._azure_speech_resource_id ,
165+ key = self ._azure_speech_key ,
166+ region = self ._azure_speech_region ,
167+ )
168+ transcript = self ._recognize_audio (audio_bytes = audio_bytes , speech_config = speech_config )
130169 except Exception as e :
131170 logger .error ("Failed to convert audio file to text: %s" , str (e ))
132171 raise
@@ -136,12 +175,44 @@ def recognize_audio(self, audio_bytes: bytes) -> str:
136175 """
137176 Recognize audio file and return transcribed text.
138177
178+ .. deprecated::
179+ Use :meth:`convert_async` instead, which resolves token providers correctly.
180+ This method does not support callable token providers.
181+
139182 Args:
140183 audio_bytes (bytes): Audio bytes input.
141184
142185 Returns:
143186 str: Transcribed text.
144187
188+ Raises:
189+ ModuleNotFoundError: If the azure.cognitiveservices.speech module is not installed.
190+ """
191+ if self ._token_provider :
192+ warnings .warn (
193+ "recognize_audio() does not support callable token providers. "
194+ "Use convert_async() instead, which correctly resolves token providers." ,
195+ DeprecationWarning ,
196+ stacklevel = 2 ,
197+ )
198+ speech_config = get_speech_config (
199+ resource_id = self ._azure_speech_resource_id ,
200+ key = self ._azure_speech_key ,
201+ region = self ._azure_speech_region ,
202+ )
203+ return self ._recognize_audio (audio_bytes = audio_bytes , speech_config = speech_config )
204+
205+ def _recognize_audio (self , * , audio_bytes : bytes , speech_config : "speechsdk.SpeechConfig" ) -> str :
206+ """
207+ Recognize audio from bytes using the given speech config.
208+
209+ Args:
210+ audio_bytes (bytes): Audio bytes input.
211+ speech_config (speechsdk.SpeechConfig): Pre-built speech configuration.
212+
213+ Returns:
214+ str: Transcribed text.
215+
145216 Raises:
146217 ModuleNotFoundError: If the azure.cognitiveservices.speech module is not installed.
147218 """
@@ -154,36 +225,25 @@ def recognize_audio(self, audio_bytes: bytes) -> str:
154225 )
155226 raise e
156227
157- speech_config = get_speech_config (
158- resource_id = self ._azure_speech_resource_id , key = self ._azure_speech_key , region = self ._azure_speech_region
159- )
160228 speech_config .speech_recognition_language = self ._recognition_language
161229
162- # Create a PullAudioInputStream from the byte stream
163230 push_stream = speechsdk .audio .PushAudioInputStream ()
164231 audio_config = speechsdk .audio .AudioConfig (stream = push_stream )
165232
166- # Instantiate a speech recognizer object
167233 speech_recognizer = speechsdk .SpeechRecognizer (speech_config = speech_config , audio_config = audio_config )
168- # Create an empty list to store recognized text
169234 transcribed_text : list [str ] = []
170- # Flag is set to False to indicate that recognition is not yet finished
171235 self .done = False
172236
173- # Connect callbacks to the events fired by the speech recognizer
174237 speech_recognizer .recognized .connect (lambda evt : self .transcript_cb (evt , transcript = transcribed_text ))
175238 speech_recognizer .recognizing .connect (lambda evt : logger .info (f"RECOGNIZING: { evt } " ))
176239 speech_recognizer .recognized .connect (lambda evt : logger .info (f"RECOGNIZED: { evt } " ))
177240 speech_recognizer .session_started .connect (lambda evt : logger .info (f"SESSION STARTED: { evt } " ))
178241 speech_recognizer .session_stopped .connect (lambda evt : logger .info (f"SESSION STOPPED: { evt } " ))
179- # Stop continuous recognition when stopped or canceled event is fired
180242 speech_recognizer .canceled .connect (lambda evt : self .stop_cb (evt , recognizer = speech_recognizer ))
181243 speech_recognizer .session_stopped .connect (lambda evt : self .stop_cb (evt , recognizer = speech_recognizer ))
182244
183- # Start continuous recognition
184245 speech_recognizer .start_continuous_recognition_async ()
185246
186- # Push the entire audio data into the stream
187247 push_stream .write (audio_bytes )
188248 push_stream .close ()
189249
0 commit comments