From b70933984e10e8ca786ce85c0e5243c927a75756 Mon Sep 17 00:00:00 2001 From: Nishchay Mahor Date: Sat, 4 Jul 2026 23:57:51 -0700 Subject: [PATCH 1/3] fix: handle SSML phoneme attribute order in ssml_to_deepgram ssml_to_deepgram only matched tags with alphabet before ph, so word silently lost its pronunciation and fell through to the generic tag strip, leaving the bare word. SSML attribute order is not significant, so match the attributes as a group and pull ph out of it. Add a regression test. --- src/deepgram/helpers/text_builder.py | 12 +++++++++--- tests/custom/test_text_builder.py | 12 ++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/deepgram/helpers/text_builder.py b/src/deepgram/helpers/text_builder.py index b859b217..3d766414 100644 --- a/src/deepgram/helpers/text_builder.py +++ b/src/deepgram/helpers/text_builder.py @@ -239,12 +239,18 @@ def ssml_to_deepgram(ssml_text: str) -> str: # Parse XML fragments manually to handle mixed content # Use regex to find and replace SSML elements - # Handle tags - phoneme_pattern = r'(.*?)' + # Handle tags. Attribute order is not significant in SSML, so match + # the attributes as a group and pull `ph` out of it rather than requiring + # `alphabet` before `ph` (which silently dropped the pronunciation otherwise). + phoneme_pattern = r"]*?)\s*>(.*?)" def replace_phoneme(match): - ipa = match.group(1) + attributes = match.group(1) word = match.group(2) + ph_match = re.search(r'ph=["\'](.*?)["\']', attributes) + if ph_match is None: + return word + ipa = ph_match.group(1) return json.dumps({"word": word, "pronounce": ipa}, ensure_ascii=False) ssml_text = re.sub(phoneme_pattern, replace_phoneme, ssml_text) diff --git a/tests/custom/test_text_builder.py b/tests/custom/test_text_builder.py index 77a7ed1b..45ca7009 100644 --- a/tests/custom/test_text_builder.py +++ b/tests/custom/test_text_builder.py @@ -234,10 +234,18 @@ def test_basic_phoneme(self): """Test converting basic phoneme tag""" ssml = 'azathioprine' result = ssml_to_deepgram(ssml) - + assert '"word": "azathioprine"' in result assert '"pronounce": "ˌæzəˈθaɪəpriːn"' in result - + + def test_phoneme_attribute_order_independent(self): + """ph before alphabet must work too (SSML attribute order is not significant)""" + ssml = 'azathioprine' + result = ssml_to_deepgram(ssml) + + assert '"word": "azathioprine"' in result + assert '"pronounce": "ˌæzəˈθaɪəpriːn"' in result + def test_basic_break(self): """Test converting break tag (milliseconds)""" ssml = '' From 72df5d7f4654250f86319358036e396f8561c93c Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Tue, 1 Sep 2026 11:18:12 +0100 Subject: [PATCH 2/3] fix: preserve IPA validation in SSML conversion --- src/deepgram/helpers/text_builder.py | 7 ++++--- tests/custom/test_text_builder.py | 23 ++++++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/deepgram/helpers/text_builder.py b/src/deepgram/helpers/text_builder.py index 3d766414..ac179f5f 100644 --- a/src/deepgram/helpers/text_builder.py +++ b/src/deepgram/helpers/text_builder.py @@ -247,10 +247,11 @@ def ssml_to_deepgram(ssml_text: str) -> str: def replace_phoneme(match): attributes = match.group(1) word = match.group(2) - ph_match = re.search(r'ph=["\'](.*?)["\']', attributes) - if ph_match is None: + alphabet_match = re.search(r'(?:^|\s)alphabet\s*=\s*(["\'])ipa\1(?=\s|$)', attributes) + ph_match = re.search(r'(?:^|\s)ph\s*=\s*(["\'])(.*?)\1(?=\s|$)', attributes) + if alphabet_match is None or ph_match is None: return word - ipa = ph_match.group(1) + ipa = ph_match.group(2) return json.dumps({"word": word, "pronounce": ipa}, ensure_ascii=False) ssml_text = re.sub(phoneme_pattern, replace_phoneme, ssml_text) diff --git a/tests/custom/test_text_builder.py b/tests/custom/test_text_builder.py index 45ca7009..a14c7571 100644 --- a/tests/custom/test_text_builder.py +++ b/tests/custom/test_text_builder.py @@ -246,6 +246,28 @@ def test_phoneme_attribute_order_independent(self): assert '"word": "azathioprine"' in result assert '"pronounce": "ˌæzəˈθaɪəpriːn"' in result + def test_phoneme_attributes_allow_whitespace_around_equals(self): + """Valid XML whitespace around attribute equals signs must be accepted""" + ssml = "azathioprine" + result = ssml_to_deepgram(ssml) + + assert '"word": "azathioprine"' in result + assert '"pronounce": "ˌæzəˈθaɪəpriːn"' in result + + @pytest.mark.parametrize( + "attributes", + [ + 'ph="test"', + 'alphabet="x-sampa" ph="test"', + 'alphabet="ipa" data-ph="test"', + ], + ) + def test_phoneme_requires_ipa_alphabet_and_ph_attribute(self, attributes): + """Unsupported or lookalike attributes must degrade to plain text""" + ssml = f"medicine" + + assert ssml_to_deepgram(ssml) == "medicine" + def test_basic_break(self): """Test converting break tag (milliseconds)""" ssml = '' @@ -505,4 +527,3 @@ def test_standalone_function_workflow(self): assert '"pronounce": "ˌæzəˈθaɪəpriːn"' in text assert '"word": "dupilumab"' in text assert '"pronounce": "duːˈpɪljuːmæb"' in text - From 0fa3bae24f58509a01a4bec2f047e3e68557f28a Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Tue, 1 Sep 2026 11:25:13 +0100 Subject: [PATCH 3/3] fix: bound SSML phoneme matching --- src/deepgram/helpers/text_builder.py | 2 +- tests/custom/test_text_builder.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/deepgram/helpers/text_builder.py b/src/deepgram/helpers/text_builder.py index ac179f5f..384b15eb 100644 --- a/src/deepgram/helpers/text_builder.py +++ b/src/deepgram/helpers/text_builder.py @@ -242,7 +242,7 @@ def ssml_to_deepgram(ssml_text: str) -> str: # Handle tags. Attribute order is not significant in SSML, so match # the attributes as a group and pull `ph` out of it rather than requiring # `alphabet` before `ph` (which silently dropped the pronunciation otherwise). - phoneme_pattern = r"]*?)\s*>(.*?)" + phoneme_pattern = r"]*?)\s*>([^<]*)" def replace_phoneme(match): attributes = match.group(1) diff --git a/tests/custom/test_text_builder.py b/tests/custom/test_text_builder.py index a14c7571..43c6270e 100644 --- a/tests/custom/test_text_builder.py +++ b/tests/custom/test_text_builder.py @@ -268,6 +268,18 @@ def test_phoneme_requires_ipa_alphabet_and_ph_attribute(self, attributes): assert ssml_to_deepgram(ssml) == "medicine" + def test_unclosed_phoneme_does_not_consume_following_phoneme(self): + """Malformed input must not capture a later valid phoneme tag""" + ssml = ( + '' + 'medicine' + ) + result = ssml_to_deepgram(ssml) + + assert '"word": "medicine"' in result + assert '"pronounce": "good"' in result + assert '"pronounce": "bad"' not in result + def test_basic_break(self): """Test converting break tag (milliseconds)""" ssml = ''