Skip to content

feat: 新增GPT_SoVIS(本地加载)的TTS服务商 #1821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Zhalslar
Copy link
Contributor

@Zhalslar Zhalslar commented Jun 16, 2025

Motivation

GPT_SoVIS是非常流行的开源免费、效果强大、可本地部署的TTS模型,之前以Astrbot_plugin_GPT_SoVIS插件形式存在,现在集成到框架中作为“gsv_tts_selfhost”提供TTS服务

Modifications

  • 新增gsv_selfhosted_source.py文件,是核心代码所在的文件
  • manager.py中加入对gsv_selfhosted_source.py的加载
  • 在默认配置中加入gsv_tts_selfhost的相关配置

Check

  • 😊 我的 Commit Message 符合良好的规范
  • [x 👀 我的更改经过良好的测试
  • 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到了 requirements.txtpyproject.toml 文件相应位置。
  • 😮 我的更改没有引入恶意代码

好的,这是翻译成中文的 pull request 总结:

Sourcery 总结

将自托管的 GPT_SoVITS TTS 服务集成到 provider 框架中,并更新相关的默认设置

新特性:

  • 添加一个新的 gsv_tts_selfhost provider 适配器,包含配置、管理器加载和在 gsv_selfhosted_source.py 中的实现

增强功能:

  • 将 reply_with_mention 和 reply_with_quote 设置从布尔标志转换为默认配置中的概率浮点数
Original summary in English

好的,这是翻译成中文的 pull request 摘要:

Sourcery 总结

将本地托管的 GPT_SoVITS TTS 提供程序集成到框架中,并更新回复配置以使用基于概率的浮点数

新功能:

  • 添加一个新的 gsv_tts_selfhost 提供程序适配器,用于 GPT_SoVITS 本地自托管 TTS

增强功能:

  • reply_with_mentionreply_with_quote 设置从布尔值转换为概率浮点数
  • 为 GPT_SoVITS TTS 参数添加默认配置模式条目,包括 API 端点、模型权重路径和生成选项
Original summary in English

Summary by Sourcery

Integrate a locally hosted GPT_SoVITS TTS provider into the framework and update reply configuration to use probability-based floats

New Features:

  • Add a new gsv_tts_selfhost provider adapter for GPT_SoVITS local self-hosted TTS

Enhancements:

  • Convert reply_with_mention and reply_with_quote settings from booleans to probability floats
  • Add default configuration schema entries for GPT_SoVITS TTS parameters including API endpoint, model weight paths, and generation options

Copy link
Contributor

sourcery-ai bot commented Jun 16, 2025

## 审查者指南

此 PR 通过添加新的 ProviderGSVTTS 适配器和更新 provider 管理器,将自托管的 GPT_SoVITS TTS 服务集成到 provider 框架中,同时扩展了默认配置模式以包含新的 TTS 设置,并将回复标志转换为概率浮点数。

#### 通过 `gsv_tts_selfhost` 发起 TTS 请求的序列图

```mermaid
sequenceDiagram
    participant C as Client/System
    participant P_GSV as ProviderGSVTTS
    participant GSV_API as "GPT-SoVITS API (Local)"

    C->>P_GSV: get_audio(text)
    activate P_GSV
    P_GSV->>GSV_API: GET /tts (text, params)
    activate GSV_API
    GSV_API-->>P_GSV: audio_bytes
    deactivate GSV_API
    P_GSV->>P_GSV: Save audio_bytes to file
    P_GSV-->>C: audio_file_path
    deactivate P_GSV

ProviderGSVTTS 初始化和模型设置的序列图

sequenceDiagram
    participant System as System
    participant P_GSV as ProviderGSVTTS
    participant GSV_API as "GPT-SoVITS API (Local)"

    System->>P_GSV: __init__(config, settings)
    activate P_GSV
    P_GSV->>P_GSV: Async task: _set_model_weights()
    alt gpt_weights_path configured
        P_GSV->>GSV_API: GET /set_gpt_weights?weights_path=...
        activate GSV_API
        GSV_API-->>P_GSV: HTTP Response
        deactivate GSV_API
    end
    alt sovits_weights_path configured
        P_GSV->>GSV_API: GET /set_sovits_weights?weights_path=...
        activate GSV_API
        GSV_API-->>P_GSV: HTTP Response
        deactivate GSV_API
    end
    P_GSV-->>System: ProviderGSVTTS instance
    deactivate P_GSV
Loading

default.py 中配置更新的 ER 图

erDiagram
    GlobalConfig {
        float reply_with_mention "已更改:bool 类型为 float 类型(0.0-1.0 概率)"
        float reply_with_quote "已更改:bool 类型为 float 类型(0.0-1.0 概率)"
        map TTS_Providers "TTS provider 配置的映射"
    }

    TTS_Providers ||--|{ GSV_TTS_ProviderConfig : "新条目:gsv_tts_selfhost"

    GSV_TTS_ProviderConfig {
        string id "例如,'gsv_tts'"
        bool enable "启用/禁用 provider"
        string type "'gsv_tts_selfhost'"
        string provider_type "'text_to_speech'"
        string api_base "本地 GPT-SoVITS API 基本 URL"
        string gpt_weights_path "GPT 模型路径(可选)"
        string sovits_weights_path "SoVITS 模型路径(可选)"
        object gsv_default_parms "默认合成参数"
    }

    GSV_TTS_ProviderConfig ||--|{ GSV_DefaultParameters : "gsv_default_parms"
    GSV_DefaultParameters {
        string gsv_ref_audio_path "参考音频路径(必需)"
        string gsv_prompt_text "参考音频提示文本(必需)"
        string gsv_prompt_lang "提示文本的语言(例如,'zh')"
        string gsv_text_lang "要合成的文本的语言(例如,'zh')"
        int gsv_top_k "Top K 采样"
        float gsv_top_p "Top P(nucleus)采样"
        float gsv_temperature "采样温度"
        string gsv_text_split_method "文本分割方法(例如,'cut3')"
        int gsv_batch_size "推理批次大小"
        float gsv_batch_threshold "批次阈值"
        bool gsv_split_bucket "启用 split bucket 处理"
        float gsv_speed_factor "语速因子"
        float gsv_fragment_interval "流式传输的片段间隔"
        bool gsv_streaming_mode "启用流式传输模式输出"
        int gsv_seed "用于重现性的随机种子"
        bool gsv_parallel_infer "启用并行推理"
        float gsv_repetition_penalty "重复惩罚因子"
        string gsv_media_type "输出音频格式(例如,'wav')"
        string gsv_aux_ref_audio_paths "辅助参考音频路径(可选)"
    }
Loading

ProviderGSVTTS 的类图

classDiagram
    class TTSProvider {
        <<Interface>>
        +get_audio(text: str) str
    }
    class ProviderGSVTTS {
        -api_base: str
        -gpt_weights_path: str
        -sovits_weights_path: str
        -default_params: dict
        -emotions: dict
        +__init__(provider_config: dict, provider_settings: dict)
        -_make_request(endpoint: str, params: dict) str | bytes
        -_set_model_weights() None
        +get_audio(text: str) str
    }
    TTSProvider <|-- ProviderGSVTTS
Loading

文件级别更改

变更 详情 文件
集成了自托管的 GPT_SoVITS TTS provider 适配器
  • 实现 ProviderGSVTTS 类来处理模型设置、请求和音频生成
  • 通过装饰器注册适配器
  • 更新 manager.load_provider 以导入 gsv_tts_selfhost
astrbot/core/provider/sources/gsv_selfhosted_source.py
astrbot/core/provider/manager.py
扩展了默认配置模式以支持本地 TTS 和概率回复标志
  • 将 reply_with_mention 和 reply_with_quote 从布尔值转换为带有概率提示的浮点数
  • 添加 GSV TTS(本地加载) provider 块,包括 id、enable 标志、API base、权重路径和默认参数
  • 为 gpt_weights_path、sovits_weights_path 和嵌套的 gsv_default_parms 字段定义新的 schema 项
astrbot/core/config/default.py

提示和命令

与 Sourcery 交互

  • 触发新的审查: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 通过回复审查评论,要求 Sourcery 从该评论创建一个 issue。 您也可以回复审查评论并使用 @sourcery-ai issue 从该评论创建一个 issue。
  • 生成 pull request 标题: 在 pull request 标题中的任何位置写入 @sourcery-ai 以随时生成标题。 您也可以在 pull request 上评论 @sourcery-ai title 以随时(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中的任何位置写入 @sourcery-ai summary 以随时在您想要的位置生成 PR 摘要。 您也可以在 pull request 上评论 @sourcery-ai summary 以随时(重新)生成摘要。
  • 生成审查者指南: 在 pull request 上评论 @sourcery-ai guide 以随时(重新)生成审查者指南。
  • 解决所有 Sourcery 评论: 在 pull request 上评论 @sourcery-ai resolve 以解决所有 Sourcery 评论。 如果您已经解决了所有评论并且不想再看到它们,这将非常有用。
  • 驳回所有 Sourcery 审查: 在 pull request 上评论 @sourcery-ai dismiss 以驳回所有现有的 Sourcery 审查。 如果您想从新的审查开始,这将特别有用 - 不要忘记评论 @sourcery-ai review 以触发新的审查!

自定义您的体验

访问您的 dashboard 以:

  • 启用或禁用审查功能,例如 Sourcery 生成的 pull request 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

```
Original review guide in English

Reviewer's Guide

This PR integrates a self-hosted GPT_SoVITS TTS service into the provider framework by adding a new ProviderGSVTTS adapter and updating the provider manager, while extending the default configuration schema to include the new TTS settings and converting reply flags to probabilistic floats.

Sequence Diagram for TTS Request via gsv_tts_selfhost

sequenceDiagram
    participant C as Client/System
    participant P_GSV as ProviderGSVTTS
    participant GSV_API as "GPT-SoVITS API (Local)"

    C->>P_GSV: get_audio(text)
    activate P_GSV
    P_GSV->>GSV_API: GET /tts (text, params)
    activate GSV_API
    GSV_API-->>P_GSV: audio_bytes
    deactivate GSV_API
    P_GSV->>P_GSV: Save audio_bytes to file
    P_GSV-->>C: audio_file_path
    deactivate P_GSV
Loading

Sequence Diagram for ProviderGSVTTS Initialization and Model Setup

sequenceDiagram
    participant System as System
    participant P_GSV as ProviderGSVTTS
    participant GSV_API as "GPT-SoVITS API (Local)"

    System->>P_GSV: __init__(config, settings)
    activate P_GSV
    P_GSV->>P_GSV: Async task: _set_model_weights()
    alt gpt_weights_path configured
        P_GSV->>GSV_API: GET /set_gpt_weights?weights_path=...
        activate GSV_API
        GSV_API-->>P_GSV: HTTP Response
        deactivate GSV_API
    end
    alt sovits_weights_path configured
        P_GSV->>GSV_API: GET /set_sovits_weights?weights_path=...
        activate GSV_API
        GSV_API-->>P_GSV: HTTP Response
        deactivate GSV_API
    end
    P_GSV-->>System: ProviderGSVTTS instance
    deactivate P_GSV
Loading

ER Diagram for Configuration Updates in default.py

erDiagram
    GlobalConfig {
        float reply_with_mention "Changed: bool to float (0.0-1.0 probability)"
        float reply_with_quote "Changed: bool to float (0.0-1.0 probability)"
        map TTS_Providers "Map of TTS provider configurations"
    }

    TTS_Providers ||--|{ GSV_TTS_ProviderConfig : "New entry: gsv_tts_selfhost"

    GSV_TTS_ProviderConfig {
        string id "e.g., 'gsv_tts'"
        bool enable "Enable/disable provider"
        string type "'gsv_tts_selfhost'"
        string provider_type "'text_to_speech'"
        string api_base "Local GPT-SoVITS API base URL"
        string gpt_weights_path "Path to GPT model (optional)"
        string sovits_weights_path "Path to SoVITS model (optional)"
        object gsv_default_parms "Default synthesis parameters"
    }

    GSV_TTS_ProviderConfig ||--|{ GSV_DefaultParameters : "gsv_default_parms"
    GSV_DefaultParameters {
        string gsv_ref_audio_path "Reference audio path (required)"
        string gsv_prompt_text "Reference audio prompt text (required)"
        string gsv_prompt_lang "Language of prompt text (e.g., 'zh')"
        string gsv_text_lang "Language of text to synthesize (e.g., 'zh')"
        int gsv_top_k "Top K sampling"
        float gsv_top_p "Top P (nucleus) sampling"
        float gsv_temperature "Sampling temperature"
        string gsv_text_split_method "Text splitting method (e.g., 'cut3')"
        int gsv_batch_size "Inference batch size"
        float gsv_batch_threshold "Batch threshold"
        bool gsv_split_bucket "Enable split bucket processing"
        float gsv_speed_factor "Speech speed factor"
        float gsv_fragment_interval "Fragment interval for streaming"
        bool gsv_streaming_mode "Enable streaming mode output"
        int gsv_seed "Random seed for reproducibility"
        bool gsv_parallel_infer "Enable parallel inference"
        float gsv_repetition_penalty "Repetition penalty factor"
        string gsv_media_type "Output audio format (e.g., 'wav')"
        string gsv_aux_ref_audio_paths "Auxiliary reference audio paths (optional)"
    }
Loading

Class Diagram for the New ProviderGSVTTS

classDiagram
    class TTSProvider {
        <<Interface>>
        +get_audio(text: str) str
    }
    class ProviderGSVTTS {
        -api_base: str
        -gpt_weights_path: str
        -sovits_weights_path: str
        -default_params: dict
        -emotions: dict
        +__init__(provider_config: dict, provider_settings: dict)
        -_make_request(endpoint: str, params: dict) str | bytes
        -_set_model_weights() None
        +get_audio(text: str) str
    }
    TTSProvider <|-- ProviderGSVTTS
Loading

File-Level Changes

Change Details Files
Integrated self-hosted GPT_SoVITS TTS provider adapter
  • Implement ProviderGSVTTS class to handle model setup, requests, and audio generation
  • Register the adapter via decorator
  • Update manager.load_provider to import gsv_tts_selfhost
astrbot/core/provider/sources/gsv_selfhosted_source.py
astrbot/core/provider/manager.py
Extended default configuration schema to support local TTS and probabilistic reply flags
  • Convert reply_with_mention and reply_with_quote from boolean to float with probability hints
  • Add GSV TTS(本地加载) provider block including id, enable flag, API base, weight paths, and default parameters
  • Define new schema items for gpt_weights_path, sovits_weights_path, and nested gsv_default_parms fields
astrbot/core/config/default.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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.

@Zhalslar - 我已经审查了你的更改 - 这里有一些反馈:

  • 在每个请求上创建一个新的 aiohttp ClientSession 可能会很昂贵 - 考虑为提供程序重用单个会话实例。
  • _set_model_weights 作为未等待的后台任务启动可能会隐藏初始化失败; 考虑在提供程序设置期间等待它或将错误显示给调用者。
  • get_audio 中,临时文件名的前缀是 gsvi_tts_,但提供程序是 gsv_tts; 更新前缀可以防止混淆。
AI 代理的提示
请解决此代码审查中的评论:
## 总体评论
- 在每个请求上创建一个新的 aiohttp ClientSession 可能会很昂贵 - 考虑为提供程序重用单个会话实例。
-`_set_model_weights` 作为未等待的后台任务启动可能会隐藏初始化失败; 考虑在提供程序设置期间等待它或将错误显示给调用者。
-`get_audio` 中,临时文件名的前缀是 `gsvi_tts_`,但提供程序是 `gsv_tts`; 更新前缀可以防止混淆。

## 单独评论

### 评论 1
<location> `astrbot/core/provider/sources/gsv_selfhosted_source.py:38` </location>
<code_context>
+
+        # 默认参数
+        raw_params = provider_config.get("gsv_default_parms", {})
+        self.default_params: dict = {
+            key.removeprefix("gsv_"): str(value).lower()
+            for key, value in raw_params.items()
+        }
+
</code_context>

<issue_to_address>
避免将所有参数转换为小写字符串

将所有值转换为小写字符串会将整数、浮点数和布尔值更改为字符串,这可能会导致 API 中的类型问题。 尽可能保留原始类型。
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        self.default_params: dict = {
            key.removeprefix("gsv_"): str(value).lower()
            for key, value in raw_params.items()
        }
=======
        self.default_params: dict = {
            key.removeprefix("gsv_"): value.lower() if isinstance(value, str) else value
            for key, value in raw_params.items()
        }
>>>>>>> REPLACE

</suggested_fix>

### 评论 2
<location> `astrbot/core/provider/sources/gsv_selfhosted_source.py:99` </location>
<code_context>
+
+        logger.debug(f"正在调用GSV语音合成接口,参数:{params}")
+
+        result = await self._make_request(endpoint, params)
+        if isinstance(result, bytes):
+            with open(path, "wb") as f:
</code_context>

<issue_to_address>
考虑使用 POST 而不是 GET 进行 TTS 请求

使用带有 JSON 主体的 POST 可以避免 URL 长度问题,并更好地支持大型文本输入。

建议的实施方式:

```python
        result = await self._make_request(endpoint, params, method="POST", json_body=True)

```

您还需要更新 `_make_request` 方法:
- 接受一个 `method` 参数(默认为 "GET" 以实现向后兼容)。
- 接受一个 `json_body` 参数(默认为 False)。
- 如果 `method` 是 "POST" 且 `json_body` 为 True,则使用 `json=params` 发送请求,而不是 `params=params`。

示例签名:
```python
async def _make_request(self, endpoint, params, method="GET", json_body=False):
    # implementation...
```
并且在实现中,使用:
```python
if method == "POST" and json_body:
    async with session.post(endpoint, json=params) as resp:
        ...
else:
    async with session.get(endpoint, params=params) as resp:
        ...
```
根据需要进行调整以适应您的代码库。
</issue_to_address>

### 评论 3
<location> `astrbot/core/provider/sources/gsv_selfhosted_source.py:66` </location>
<code_context>
+            if self.gpt_weights_path:
+                gpt_endpoint = f"{self.api_base}/set_gpt_weights"
+                gpt_params = {"weights_path": self.gpt_weights_path}
+                if await self._make_request(endpoint=gpt_endpoint, params=gpt_params):
+                    logger.info(f"成功设置 GPT 模型路径:{self.gpt_weights_path}")
+            else:
</code_context>

<issue_to_address>
不要依赖响应内容的真值来检查成功

依赖响应的真值可能会导致误报。 而是,检查 HTTP 状态代码或让 `_make_request` 在非 200 响应时引发异常。
</issue_to_address>

Sourcery 对开源是免费的 - 如果您喜欢我们的评论,请考虑分享它们 ✨
帮助我更有用! 请点击每个评论上的 👍 或 👎,我将使用反馈来改进您的评论。
Original comment in English

Hey @Zhalslar - I've reviewed your changes - here's some feedback:

  • Creating a new aiohttp ClientSession on every request can be expensive—consider reusing a single session instance for the provider.
  • Launching _set_model_weights as an unawaited background task may hide initialization failures; consider awaiting it during provider setup or surface errors to the caller.
  • In get_audio, the temp filename is prefixed with gsvi_tts_ but the provider is gsv_tts; updating the prefix would prevent confusion.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Creating a new aiohttp ClientSession on every request can be expensive—consider reusing a single session instance for the provider.
- Launching `_set_model_weights` as an unawaited background task may hide initialization failures; consider awaiting it during provider setup or surface errors to the caller.
- In `get_audio`, the temp filename is prefixed with `gsvi_tts_` but the provider is `gsv_tts`; updating the prefix would prevent confusion.

## Individual Comments

### Comment 1
<location> `astrbot/core/provider/sources/gsv_selfhosted_source.py:38` </location>
<code_context>
+
+        # 默认参数
+        raw_params = provider_config.get("gsv_default_parms", {})
+        self.default_params: dict = {
+            key.removeprefix("gsv_"): str(value).lower()
+            for key, value in raw_params.items()
+        }
+
</code_context>

<issue_to_address>
Avoid converting all params to lowercase strings

Converting all values to lowercase strings changes ints, floats, and bools to strings, which can cause type issues in the API. Preserve original types where possible.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        self.default_params: dict = {
            key.removeprefix("gsv_"): str(value).lower()
            for key, value in raw_params.items()
        }
=======
        self.default_params: dict = {
            key.removeprefix("gsv_"): value.lower() if isinstance(value, str) else value
            for key, value in raw_params.items()
        }
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `astrbot/core/provider/sources/gsv_selfhosted_source.py:99` </location>
<code_context>
+
+        logger.debug(f"正在调用GSV语音合成接口,参数:{params}")
+
+        result = await self._make_request(endpoint, params)
+        if isinstance(result, bytes):
+            with open(path, "wb") as f:
</code_context>

<issue_to_address>
Consider using POST instead of GET for TTS requests

Using POST with a JSON body avoids URL length issues and better supports large text inputs.

Suggested implementation:

```python
        result = await self._make_request(endpoint, params, method="POST", json_body=True)

```

You will also need to update the `_make_request` method to:
- Accept a `method` parameter (defaulting to "GET" for backward compatibility).
- Accept a `json_body` parameter (defaulting to False).
- If `method` is "POST" and `json_body` is True, send the request with `json=params` instead of `params=params`.

Example signature:
```python
async def _make_request(self, endpoint, params, method="GET", json_body=False):
    # implementation...
```
And in the implementation, use:
```python
if method == "POST" and json_body:
    async with session.post(endpoint, json=params) as resp:
        ...
else:
    async with session.get(endpoint, params=params) as resp:
        ...
```
Adjust as needed to fit your codebase.
</issue_to_address>

### Comment 3
<location> `astrbot/core/provider/sources/gsv_selfhosted_source.py:66` </location>
<code_context>
+            if self.gpt_weights_path:
+                gpt_endpoint = f"{self.api_base}/set_gpt_weights"
+                gpt_params = {"weights_path": self.gpt_weights_path}
+                if await self._make_request(endpoint=gpt_endpoint, params=gpt_params):
+                    logger.info(f"成功设置 GPT 模型路径:{self.gpt_weights_path}")
+            else:
</code_context>

<issue_to_address>
Don’t rely on truthiness of response content for success check

Relying on the response's truthiness may lead to false positives. Instead, check the HTTP status code or have `_make_request` raise on non-200 responses.
</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 38 to 41
self.default_params: dict = {
key.removeprefix("gsv_"): str(value).lower()
for key, value in raw_params.items()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: 避免将所有参数转换为小写字符串

将所有值转换为小写字符串会将整数、浮点数和布尔值更改为字符串,这可能会导致 API 中的类型问题。 尽可能保留原始类型。

Suggested change
self.default_params: dict = {
key.removeprefix("gsv_"): str(value).lower()
for key, value in raw_params.items()
}
self.default_params: dict = {
key.removeprefix("gsv_"): value.lower() if isinstance(value, str) else value
for key, value in raw_params.items()
}
Original comment in English

suggestion: Avoid converting all params to lowercase strings

Converting all values to lowercase strings changes ints, floats, and bools to strings, which can cause type issues in the API. Preserve original types where possible.

Suggested change
self.default_params: dict = {
key.removeprefix("gsv_"): str(value).lower()
for key, value in raw_params.items()
}
self.default_params: dict = {
key.removeprefix("gsv_"): value.lower() if isinstance(value, str) else value
for key, value in raw_params.items()
}

if self.gpt_weights_path:
gpt_endpoint = f"{self.api_base}/set_gpt_weights"
gpt_params = {"weights_path": self.gpt_weights_path}
if await self._make_request(endpoint=gpt_endpoint, params=gpt_params):
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): 不要依赖响应内容的真值来检查成功

依赖响应的真值可能会导致误报。 而是,检查 HTTP 状态代码或让 _make_request 在非 200 响应时引发异常。

Original comment in English

issue (bug_risk): Don’t rely on truthiness of response content for success check

Relying on the response's truthiness may lead to false positives. Instead, check the HTTP status code or have _make_request raise on non-200 responses.

f.write(result)
return path
else:
raise Exception(f"GSVI TTS API 请求失败: {result}")
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): 引发一个特定的错误,而不是通用的 ExceptionBaseException (raise-specific-error)

Explanation如果一段代码引发一个特定的异常类型, 而不是通用的 [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) 或 [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), 调用代码可以:
  • 获取更多关于它是什么类型的错误的信息
  • 为它定义特定的异常处理

这样,代码的调用者可以适当地处理错误。

您如何解决这个问题?

因此,与其让代码引发 ExceptionBaseException,例如

if incorrect_input(value):
    raise Exception("The input is incorrect")

您可以让代码引发一个特定的错误,例如

if incorrect_input(value):
    raise ValueError("The input is incorrect")

或者

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")
Original comment in English

issue (code-quality): Raise a specific error instead of the general Exception or BaseException (raise-specific-error)

ExplanationIf a piece of code raises a specific exception type rather than the generic [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), the calling code can:
  • get more information about what type of error it is
  • define specific exception handling for it

This way, callers of the code can handle the error appropriately.

How can you solve this?

So instead of having code raising Exception or BaseException like

if incorrect_input(value):
    raise Exception("The input is incorrect")

you can have code raising a specific error like

if incorrect_input(value):
    raise ValueError("The input is incorrect")

or

class IncorrectInputError(Exception):
    pass


if incorrect_input(value):
    raise IncorrectInputError("The input is incorrect")

Comment on lines 100 to 105
if isinstance(result, bytes):
with open(path, "wb") as f:
f.write(result)
return path
else:
raise Exception(f"GSVI TTS API 请求失败: {result}")
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): 我们发现了这些问题:

Original comment in English

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

},
"reply_with_quote": {
"description": "回复时引用消息",
"type": "bool",
"hint": "启用后,机器人回复消息时会引用原消息。实际效果以具体的平台适配器为准。",
"type": "float",
Copy link
Member

Choose a reason for hiding this comment

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

这个功能和这个 PR 不太相关,并且好像没看到具体逻辑的变动,可以放到别的 PR 去实现

async with aiohttp.ClientSession() as session:
async with session.request("GET", endpoint, params=params) as response:
if response.status != 200:
return await response.text()
Copy link
Member

@Soulter Soulter Jun 16, 2025

Choose a reason for hiding this comment

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

如果不是 200,这里是返回空响应吗?建议这里直接 raise,然后在调用的时候处理异常

@Zhalslar Zhalslar requested a review from Soulter June 16, 2025 14:41
@Zhalslar
Copy link
Contributor Author

呃,提交上来才发现另一个PR串了

@Zhalslar
Copy link
Contributor Author

已优化

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.

2 participants