Skip to content

Conversation

lacser
Copy link

@lacser lacser commented Oct 5, 2025

2553

fixes #2553


Motivation / 动机

修复了CosyVoice V2,Qwen TTS生成报错的问题。Fixed compatability problems with CosyVoice V2, Qwen TTS.

Modifications / 改动点

只更改了dashscope_tts.py这一个文件。更新了API调用方式,CosyVoice与Qwen TTS使用不同格式调用。

Verification Steps / 验证步骤

添加CosyVoice V2,Qwen TTS模型,先测试服务提供商可用性。然后在聊天中开启TTS语音生成功能,实际验证。
为了方便测试,可以使用下面的配置。注意替换API Key

{
      "id": "Qwen TTS",
      "provider": "dashscope",
      "type": "dashscope_tts",
      "provider_type": "text_to_speech",
      "enable": true,
      "api_key": "sk-YOUR API KEY",
      "model": "qwen-tts",
      "dashscope_tts_voice": "Cherry",
      "timeout": 20
},
{
      "id": "CozyVoice V2",
      "provider": "dashscope",
      "type": "dashscope_tts",
      "provider_type": "text_to_speech",
      "enable": true,
      "api_key": "sk-YOUR API KEY",
      "model": "cosyvoice-v2",
      "dashscope_tts_voice": "longnan_v2",
      "timeout": 20
}

Screenshots or Test Results / 运行截图或测试结果

image Screenshot_20251005-144812

Compatibility & Breaking Changes / 兼容性与破坏性变更

  • 这是一个破坏性变更 (Breaking Change)。/ This is a breaking change.
  • 这不是一个破坏性变更。/ This is NOT a breaking change.

Checklist / 检查清单

  • 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。/ If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
  • 👀 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。/ My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
  • 😮 我的更改没有引入恶意代码。/ My changes do not introduce malicious code.

Sourcery 总结

通过分离 CosyVoice V2 和 Qwen TTS 模型的合成流程,并添加响应解析、错误处理和正确的文件扩展名处理,修复了 dashscope TTS 提供者中的兼容性问题。

错误修复:

  • 通过更新 API 调用逻辑,解决了在使用 CosyVoice V2 和 Qwen TTS 模型生成音频时出现的错误。
  • 针对模型配置缺失或音频合成失败的情况,抛出清晰的运行时错误。

功能增强:

  • 重构 get_audio 方法,根据模型类型将请求路由到 _synthesize_with_qwen_tts_synthesize_with_cosyvoice
  • 实现辅助方法,用于检测 Qwen TTS 模型、从 base64 或 URL 中提取音频字节,并格式化 SDK 错误响应。
Original summary in English

Summary by Sourcery

Fix compatibility issues in dashscope TTS provider by separating synthesis flows for CosyVoice V2 and Qwen TTS models, adding response parsing, error handling, and correct file extension handling.

Bug Fixes:

  • Resolve errors when generating audio with CosyVoice V2 and Qwen TTS models by updating the API invocation logic.
  • Raise clear runtime errors for missing model configuration or failed audio synthesis.

Enhancements:

  • Refactor get_audio to route requests to _synthesize_with_qwen_tts or _synthesize_with_cosyvoice based on model type.
  • Implement helper methods to detect Qwen TTS models, extract audio bytes from base64 or URLs, and format SDK error responses.

@auto-assign auto-assign bot requested review from anka-afk and Larch-C October 5, 2025 18:49
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `astrbot/core/provider/sources/dashscope_tts.py:115` </location>
<code_context>
+        try:
+            with urlopen(url, timeout=timeout) as response:
+                return response.read()
+        except (URLError, TimeoutError, OSError):
+            return None
+
</code_context>

<issue_to_address>
**suggestion:** Consider logging or surfacing download errors for troubleshooting.

Logging the exception details instead of silently returning None will make it easier to identify and resolve download issues.

Suggested implementation:

```python
        try:
            with urlopen(url, timeout=timeout) as response:
                return response.read()
        except (URLError, TimeoutError, OSError) as e:
            logger.error(f"Failed to download audio from URL '{url}': {e}", exc_info=True)
            return None

```

```python
import logging

logger = logging.getLogger(__name__)

from ..entities import ProviderType
from ..provider import TTSProvider

```
</issue_to_address>

### Comment 2
<location> `astrbot/core/provider/sources/dashscope_tts.py:91` </location>
<code_context>
    def _extract_audio_from_response(self, response) -> Optional[bytes]:
        output = getattr(response, "output", None)
        audio_obj = getattr(output, "audio", None) if output is not None else None
        if not audio_obj:
            return None

        data_b64 = getattr(audio_obj, "data", None)
        if data_b64:
            try:
                return base64.b64decode(data_b64)
            except (ValueError, TypeError):
                return None

        url = getattr(audio_obj, "url", None)
        if url:
            return self._download_audio_from_url(url)
        return None

</code_context>

<issue_to_address>
**issue (code-quality):** Use named expression to simplify assignment and conditional [×2] ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
</issue_to_address>

### Comment 3
<location> `astrbot/core/provider/sources/dashscope_tts.py:154-156` </location>
<code_context>
    def _format_dashscope_error(self, response) -> str:
        status_code = getattr(response, "status_code", None)
        code = getattr(response, "code", None)
        message = getattr(response, "message", None)
        parts = []
        if status_code is not None:
            parts.append(f"status_code={status_code}")
        if code:
            parts.append(f"code={code}")
        if message:
            parts.append(f"message={message}")
        if not parts:
            return ""
        return " ".join(parts)

</code_context>

<issue_to_address>
**suggestion (code-quality):** We've found these issues:

- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))

```suggestion
        return "" if not parts else " ".join(parts)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +154 to +156
if not parts:
return ""
return " ".join(parts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): We've found these issues:

Suggested change
if not parts:
return ""
return " ".join(parts)
return "" if not parts else " ".join(parts)

@Soulter Soulter changed the title fix: 修复了CosyVoice V2,Qwen TTS生成报错的问题。Fixed compatability problems with CosyVoice V2, Qwen TTS. fix: 修复 CosyVoice V2,Qwen TTS 生成报错的问题 Oct 6, 2025
Copy link
Member

@Soulter Soulter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要换成 aiohttp 进行异步网络请求,否则在请求时会堵塞整个bot

@lacser
Copy link
Author

lacser commented Oct 6, 2025

已经替换为aiohttp以异步下载音频。经过再次测试后运行正常。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]阿里云TTS使用 cosyvoice-v2 会出现错误
2 participants