From d2a9df36d112d9b5a85e356d95cf813ff71dab5b Mon Sep 17 00:00:00 2001 From: didar Date: Sat, 29 Aug 2026 01:23:23 +0500 Subject: [PATCH] feat: add cache_fingerprint method to TTSProvider and its subclasses for voice/model configuration --- services/ai_voice_runtime_service/ack_bank.py | 1 + .../ai_voice_runtime_service/providers/tts.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/services/ai_voice_runtime_service/ack_bank.py b/services/ai_voice_runtime_service/ack_bank.py index 3c6d5ce..2b702d3 100644 --- a/services/ai_voice_runtime_service/ack_bank.py +++ b/services/ai_voice_runtime_service/ack_bank.py @@ -49,6 +49,7 @@ class PrebakedAckBank: ) -> str: payload = { "provider": getattr(self._tts_provider, "name", "tts"), + "voice_fingerprint": self._tts_provider.cache_fingerprint(language, style_hints=style_hints), "language": str(language or "").strip() or None, "style_hints": style_hints or {}, "text": text, diff --git a/services/ai_voice_runtime_service/providers/tts.py b/services/ai_voice_runtime_service/providers/tts.py index 2c96234..ac3a935 100644 --- a/services/ai_voice_runtime_service/providers/tts.py +++ b/services/ai_voice_runtime_service/providers/tts.py @@ -199,6 +199,16 @@ class TTSSynthesis: class TTSProvider: name = "stub" + def cache_fingerprint(self, language: str | None, *, style_hints: dict[str, object] | None = None) -> str: + """Identifies the exact voice/model config that would render `language`. + + Callers that cache synthesized audio keyed only on (provider, language, + text) — like the prebaked ack bank — must fold this in too, or a voice + change silently keeps serving audio recorded with the old voice. + """ + del language, style_hints + return "" + def synthesize( self, text: str, @@ -257,6 +267,10 @@ class OpenAITTSProvider(TTSProvider): def _voice(self, language: str | None) -> str: return self._kz_voice if _normalize_voice_language(language) == "kz" else self._ru_voice + def cache_fingerprint(self, language: str | None, *, style_hints: dict[str, object] | None = None) -> str: + del style_hints + return f"{self._voice(language)}:{self._model(language)}:{self._speed}" + def _cache_key(self, text: str, *, language: str | None) -> str: payload = { "provider": self.name, @@ -433,6 +447,12 @@ class YandexTTSProvider(TTSProvider): return explicit_role return self._role + def cache_fingerprint(self, language: str | None, *, style_hints: dict[str, object] | None = None) -> str: + return ( + f"{self._voice(language)}:{self._effective_role(style_hints=style_hints)}:" + f"{self._speed}:{self._sample_rate_hz}" + ) + def _cache_key(self, text: str, *, language: str | None, style_hints: dict[str, object] | None = None) -> str: payload = { "provider": self.name, @@ -643,6 +663,13 @@ class ElevenLabsTTSProvider(TTSProvider): def _language_code(self, language: str | None) -> str: return self._kz_language_code if _normalize_voice_language(language) == "kz" else self._ru_language_code + def cache_fingerprint(self, language: str | None, *, style_hints: dict[str, object] | None = None) -> str: + del style_hints + return ( + f"{self._voice(language)}:{self._model(language)}:" + f"{self._language_code(language)}:{self._output_format}" + ) + def _cache_key( self, text: str,