From d32ff9a1998991a8520bf9018a91160cb2a3d9fb Mon Sep 17 00:00:00 2001 From: xiongyuyang <112962001+xiongyuyang@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:31:36 +0800 Subject: [PATCH] fix: bind CosyVoice request authentication to its provider --- .../core/provider/sources/dashscope_tts.py | 8 ++- tests/unit/test_dashscope_tts_auth.py | 65 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_dashscope_tts_auth.py diff --git a/astrbot/core/provider/sources/dashscope_tts.py b/astrbot/core/provider/sources/dashscope_tts.py index 00c082b6b8..b2d1618bab 100644 --- a/astrbot/core/provider/sources/dashscope_tts.py +++ b/astrbot/core/provider/sources/dashscope_tts.py @@ -143,7 +143,13 @@ async def _synthesize_with_cosyvoice( ) -> tuple[bytes | None, str]: synthesizer = SpeechSynthesizer( headers={ - name.lower(): value for name, value in self.request_headers.items() + **{ + name.lower(): value + for name, value in self.request_headers.items() + if name.lower() != "authorization" + }, + # Override the SDK's global-key header for this provider only. + "Authorization": f"Bearer {self.chosen_api_key}", }, model=model, voice=self.voice, diff --git a/tests/unit/test_dashscope_tts_auth.py b/tests/unit/test_dashscope_tts_auth.py new file mode 100644 index 0000000000..b0a95e020c --- /dev/null +++ b/tests/unit/test_dashscope_tts_auth.py @@ -0,0 +1,65 @@ +import asyncio + +import dashscope +import pytest + +import astrbot.api # noqa: F401 +from astrbot.core.provider.sources import dashscope_tts + + +@pytest.mark.asyncio +@pytest.mark.parametrize("auth_header", [None, "authorization", "Authorization"]) +async def test_cosyvoice_uses_own_key_after_other_provider_initializes( + monkeypatch, auth_header +): + monkeypatch.setattr(dashscope, "api_key", "initial-key") + providers = [ + dashscope_tts.ProviderDashscopeTTSAPI( + { + "api_key": key, + "model": "cosyvoice-v2", + "custom_headers": {auth_header: "Bearer stale"} if auth_header else {}, + }, + {}, + ) + for key in ("provider-a", "provider-b") + ] + observed = {} + + def capture_request(synthesizer, text, timeout): + headers = synthesizer.request.get_websocket_headers( + synthesizer.headers, synthesizer.workspace + ) + observed[text] = [ + value for name, value in headers.items() if name.lower() == "authorization" + ] + return b"audio" + + monkeypatch.setattr(dashscope_tts.SpeechSynthesizer, "call", capture_request) + await asyncio.gather( + *( + p._synthesize_with_cosyvoice(p.get_model(), str(i)) + for i, p in enumerate(providers) + ) + ) + + assert observed == {"0": ["Bearer provider-a"], "1": ["Bearer provider-b"]} + + +def test_qwen_passes_own_key_after_other_provider_initializes(monkeypatch): + monkeypatch.setattr(dashscope, "api_key", "initial-key") + provider = dashscope_tts.ProviderDashscopeTTSAPI( + {"api_key": "provider-a", "model": "qwen-tts"}, {} + ) + dashscope_tts.ProviderDashscopeTTSAPI( + {"api_key": "provider-b", "model": "cosyvoice-v2"}, {} + ) + observed = {} + + def capture_request(**kwargs): + observed.update(kwargs) + + monkeypatch.setattr(dashscope_tts.MultiModalConversation, "call", capture_request) + provider._call_qwen_tts(provider.get_model(), "hello") + + assert observed["api_key"] == "provider-a"