feat(tts): 动态加载动作参数的语音风格

重构 TTS 插件,以便根据配置文件动态更新 `voice_style` 参数描述。

之前,可用语音风格列表是在模块导入时静态加载的。如果在不重启服务器的情况下更改了配置,planer 可能会使用过时的列表。

此更改引入了两种动态更新机制:
- 现在 `TTSVoiceAction` 构造函数会为每个实例更新参数描述。
- 重写了 `get_action_info` 类方法,以便 planner 查询动作详情时可以获取最新的风格。

这确保 LLM 始终能够访问最新的可用语音风格列表,提高 TTS 功能调用的可靠性。

此外,TTS 插件现在在模板配置中默认为禁用,以防在新设置中意外启用。
This commit is contained in:
tt-P607
2025-12-06 22:04:51 +08:00
parent fa8555aeb7
commit b372cb8fe0
3 changed files with 107 additions and 44 deletions

View File

@@ -58,7 +58,7 @@ class ChatterPlanFilter:
prompt, used_message_id_list = await self._build_prompt(plan)
plan.llm_prompt = prompt
if global_config.debug.show_prompt:
logger.info(
logger.debug(
f"规划器原始提示词:{prompt}"
) # 叫你不要改你耳朵聋吗😡😡😡😡😡

View File

@@ -16,46 +16,6 @@ from ..services.manager import get_service
logger = get_logger("tts_voice_plugin.action")
def _get_available_styles() -> list[str]:
"""动态读取配置文件获取所有可用的TTS风格名称"""
try:
# 这个路径构建逻辑是为了确保无论从哪里启动,都能准确定位到配置文件
plugin_file = Path(__file__).resolve()
# Bot/src/plugins/built_in/tts_voice_plugin/actions -> Bot
bot_root = plugin_file.parent.parent.parent.parent.parent.parent
config_file = bot_root / "config" / "plugins" / "tts_voice_plugin" / "config.toml"
if not config_file.is_file():
logger.warning("在 tts_action 中未找到 tts_voice_plugin 的配置文件,无法动态加载风格列表。")
return ["default"]
config = toml.loads(config_file.read_text(encoding="utf-8"))
styles_config = config.get("tts_styles", [])
if not isinstance(styles_config, list):
return ["default"]
# 使用显式循环和类型检查来提取 style_name以确保 Pylance 类型检查通过
style_names: list[str] = []
for style in styles_config:
if isinstance(style, dict):
name = style.get("style_name")
# 确保 name 是一个非空字符串
if isinstance(name, str) and name:
style_names.append(name)
return style_names if style_names else ["default"]
except Exception as e:
logger.error(f"动态加载TTS风格列表时出错: {e}")
return ["default"] # 出现任何错误都回退
# 在类定义之前执行函数,获取风格列表
AVAILABLE_STYLES = _get_available_styles()
STYLE_OPTIONS_DESC = ", ".join(f"'{s}'" for s in AVAILABLE_STYLES)
class TTSVoiceAction(BaseAction):
"""
通过关键词或规划器自动触发 TTS 语音合成
@@ -75,7 +35,7 @@ class TTSVoiceAction(BaseAction):
},
"voice_style": {
"type": "string",
"description": f"语音的风格。可用选项: [{STYLE_OPTIONS_DESC}]。请根据对话的情感和上下文选择一个最合适的风格。如果未提供,将使用默认风格。",
"description": "语音的风格。请根据对话的情感和上下文选择一个最合适的风格。如果未提供,将使用默认风格。",
"required": False
},
"text_language": {
@@ -115,6 +75,109 @@ class TTSVoiceAction(BaseAction):
super().__init__(*args, **kwargs)
# 关键配置项现在由 TTSService 管理
self.tts_service = get_service("tts")
# 动态更新 voice_style 参数描述(包含可用风格)
self._update_voice_style_parameter()
def _update_voice_style_parameter(self):
"""动态更新 voice_style 参数描述,包含实际可用的风格选项"""
try:
available_styles = self._get_available_styles_safe()
if available_styles:
styles_list = "".join(available_styles)
updated_description = (
f"语音的风格。请根据对话的情感和上下文选择一个最合适的风格。"
f"当前可用风格:{styles_list}。如果未提供,将使用默认风格。"
)
# 更新实例的参数描述
self.action_parameters["voice_style"]["description"] = updated_description
logger.debug(f"{self.log_prefix} 已更新语音风格参数描述,包含 {len(available_styles)} 个可用风格")
else:
logger.warning(f"{self.log_prefix} 无法获取可用语音风格,使用默认参数描述")
except Exception as e:
logger.error(f"{self.log_prefix} 更新语音风格参数时出错: {e}")
def _get_available_styles_safe(self) -> list[str]:
"""安全地获取可用语音风格列表"""
try:
# 首先尝试从TTS服务获取
if hasattr(self.tts_service, 'get_available_styles'):
styles = self.tts_service.get_available_styles()
if styles:
return styles
# 回退到直接读取配置文件
plugin_file = Path(__file__).resolve()
bot_root = plugin_file.parent.parent.parent.parent.parent.parent
config_file = bot_root / "config" / "plugins" / "tts_voice_plugin" / "config.toml"
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
config = toml.load(f)
styles_config = config.get('tts_styles', [])
if isinstance(styles_config, list):
style_names = []
for style in styles_config:
if isinstance(style, dict):
name = style.get('style_name')
if isinstance(name, str) and name:
style_names.append(name)
return style_names if style_names else ['default']
except Exception as e:
logger.debug(f"{self.log_prefix} 获取可用语音风格时出错: {e}")
return ['default'] # 安全回退
@classmethod
def get_action_info(cls) -> "ActionInfo":
"""重写获取Action信息的方法动态更新参数描述"""
# 先调用父类方法获取基础信息
info = super().get_action_info()
# 尝试动态更新 voice_style 参数描述
try:
# 尝试获取可用风格(不创建完整实例)
available_styles = cls._get_available_styles_for_info()
if available_styles:
styles_list = "".join(available_styles)
updated_description = (
f"语音的风格。请根据对话的情感和上下文选择一个最合适的风格。"
f"当前可用风格:{styles_list}。如果未提供,将使用默认风格。"
)
# 更新参数描述
info.action_parameters["voice_style"]["description"] = updated_description
except Exception as e:
logger.debug(f"[TTSVoiceAction] 在获取Action信息时更新参数描述失败: {e}")
return info
@classmethod
def _get_available_styles_for_info(cls) -> list[str]:
"""为 get_action_info 方法获取可用风格(类方法版本)"""
try:
# 构建配置文件路径
plugin_file = Path(__file__).resolve()
bot_root = plugin_file.parent.parent.parent.parent.parent.parent
config_file = bot_root / "config" / "plugins" / "tts_voice_plugin" / "config.toml"
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
config = toml.load(f)
styles_config = config.get('tts_styles', [])
if isinstance(styles_config, list):
style_names = []
for style in styles_config:
if isinstance(style, dict):
name = style.get('style_name')
if isinstance(name, str) and name:
style_names.append(name)
return style_names if style_names else ['default']
except Exception:
pass
return ['default'] # 安全回退
async def go_activate(self, llm_judge_model=None) -> bool:
"""

View File

@@ -28,7 +28,7 @@ class TTSVoicePlugin(BasePlugin):
plugin_description = "基于GPT-SoVITS的文本转语音插件重构版"
plugin_version = "3.1.2"
plugin_author = "Kilo Code & 靚仔"
enable_plugin = True
# enable_plugin 应该由配置文件控制,不在此处硬编码
config_file_name = "config.toml"
dependencies: ClassVar[list[str]] = []
@@ -61,7 +61,7 @@ class TTSVoicePlugin(BasePlugin):
default_config_content = """# 插件基础配置
[plugin]
enable = true
enable = false
keywords = [
"发语音", "语音", "说句话", "用语音说", "听你", "听声音", "想听你", "想听声音",
"讲个话", "说段话", "念一下", "读一下", "用嘴说", "", "能发语音吗","亲口"