refactor(reaction): 重构关键词反应系统为通用反应规则系统
将原有的 `keyword_reaction` 系统重构为一个更通用、更强大的 `reaction` 系统。新系统统一了关键词和正则表达式规则,并增加了按聊天流ID(`chat_stream_id`)进行规则作用域限定的功能。 主要变更包括: - **统一配置模型**:将 `KeywordReactionConfig` 和 `KeywordRuleConfig` 合并重构为 `ReactionConfig` 和 `ReactionRuleConfig`,提供了更清晰、统一的规则定义方式。 - **增加作用域控制**:新的 `ReactionRuleConfig` 增加了 `chat_stream_id` 字段,允许规则被限定在全局范围或特定的聊天会话中。 - **简化逻辑实现**:`DefaultReplyer` 中的实现被重构,以适应新的配置模型,能够筛选并应用适用范围内的规则。 - **更新配置文件模板**:`bot_config_template.toml` 已更新,以反映新的 `[[reaction.rules]]` 配置结构,并提供了详细的注释说明。 - **新增辅助方法**:在 `ChatStream` 中添加了 `get_raw_id()` 方法,用于获取未哈希的原始聊天流ID,以支持新系统的作用域匹配。 BREAKING CHANGE: 关键词反应功能的配置结构已完全改变。旧的 `[keyword_reaction]` 配置不再兼容。用户需要将原有的 `keyword_rules` 和 `regex_rules` 迁移到新的 `[[reaction.rules]]` 格式。
This commit is contained in:
@@ -156,6 +156,13 @@ class ChatStream:
|
||||
|
||||
return instance
|
||||
|
||||
def get_raw_id(self) -> str:
|
||||
"""获取原始的、未哈希的聊天流ID字符串"""
|
||||
if self.group_info:
|
||||
return f"{self.platform}:{self.group_info.group_id}:group"
|
||||
else:
|
||||
return f"{self.platform}:{self.user_info.user_id}:private"
|
||||
|
||||
def update_active_time(self):
|
||||
"""更新最后活跃时间"""
|
||||
self.last_active_time = time.time()
|
||||
@@ -256,18 +263,18 @@ class ChatStream:
|
||||
def _prepare_additional_config(self, message_info) -> str | None:
|
||||
"""
|
||||
准备 additional_config,将 format_info 嵌入其中
|
||||
|
||||
|
||||
这个方法模仿 storage.py 中的逻辑,确保 DatabaseMessages 中的 additional_config
|
||||
包含 format_info,使得 action_modifier 能够正确获取适配器支持的消息类型
|
||||
|
||||
|
||||
Args:
|
||||
message_info: BaseMessageInfo 对象
|
||||
|
||||
|
||||
Returns:
|
||||
str | None: JSON 字符串格式的 additional_config,如果为空则返回 None
|
||||
"""
|
||||
import orjson
|
||||
|
||||
|
||||
# 首先获取adapter传递的additional_config
|
||||
additional_config_data = {}
|
||||
if hasattr(message_info, 'additional_config') and message_info.additional_config:
|
||||
|
||||
@@ -796,44 +796,63 @@ class DefaultReplyer:
|
||||
async def build_keywords_reaction_prompt(self, target: str | None) -> str:
|
||||
"""构建关键词反应提示
|
||||
|
||||
该方法根据配置的关键词和正则表达式规则,
|
||||
检查目标消息内容是否触发了任何反应。
|
||||
如果匹配成功,它会生成一个包含所有触发反应的提示字符串,
|
||||
用于指导LLM的回复。
|
||||
|
||||
Args:
|
||||
target: 目标消息内容
|
||||
|
||||
Returns:
|
||||
str: 关键词反应提示字符串
|
||||
str: 关键词反应提示字符串,如果没有触发任何反应则为空字符串
|
||||
"""
|
||||
# 关键词检测与反应
|
||||
keywords_reaction_prompt = ""
|
||||
if target is None:
|
||||
return ""
|
||||
|
||||
reaction_prompt = ""
|
||||
try:
|
||||
# 添加None检查,防止NoneType错误
|
||||
if target is None:
|
||||
return keywords_reaction_prompt
|
||||
current_chat_stream_id_str = self.chat_stream.get_raw_id()
|
||||
# 2. 筛选适用的规则(全局规则 + 特定于当前聊天的规则)
|
||||
applicable_rules = []
|
||||
for rule in global_config.reaction.rules:
|
||||
if rule.chat_stream_id == "" or rule.chat_stream_id == current_chat_stream_id_str:
|
||||
applicable_rules.append(rule) # noqa: PERF401
|
||||
|
||||
# 处理关键词规则
|
||||
for rule in global_config.keyword_reaction.keyword_rules:
|
||||
if any(keyword in target for keyword in rule.keywords):
|
||||
logger.info(f"检测到关键词规则:{rule.keywords},触发反应:{rule.reaction}")
|
||||
keywords_reaction_prompt += f"{rule.reaction},"
|
||||
# 3. 遍历适用规则并执行匹配
|
||||
for rule in applicable_rules:
|
||||
matched = False
|
||||
if rule.rule_type == "keyword":
|
||||
if any(keyword in target for keyword in rule.patterns):
|
||||
logger.info(f"检测到关键词规则:{rule.patterns},触发反应:{rule.reaction}")
|
||||
reaction_prompt += f"{rule.reaction},"
|
||||
matched = True
|
||||
|
||||
elif rule.rule_type == "regex":
|
||||
for pattern_str in rule.patterns:
|
||||
try:
|
||||
pattern = re.compile(pattern_str)
|
||||
if result := pattern.search(target):
|
||||
reaction = rule.reaction
|
||||
# 替换命名捕获组
|
||||
for name, content in result.groupdict().items():
|
||||
reaction = reaction.replace(f"[{name}]", content)
|
||||
logger.info(f"匹配到正则表达式:{pattern_str},触发反应:{reaction}")
|
||||
reaction_prompt += f"{reaction},"
|
||||
matched = True
|
||||
break # 一个正则规则里只要有一个 pattern 匹配成功即可
|
||||
except re.error as e:
|
||||
logger.error(f"正则表达式编译错误: {pattern_str}, 错误信息: {e!s}")
|
||||
continue
|
||||
|
||||
if matched:
|
||||
# 如果需要每条消息只触发一个反应规则,可以在这里 break
|
||||
pass
|
||||
|
||||
# 处理正则表达式规则
|
||||
for rule in global_config.keyword_reaction.regex_rules:
|
||||
for pattern_str in rule.regex:
|
||||
try:
|
||||
pattern = re.compile(pattern_str)
|
||||
if result := pattern.search(target):
|
||||
reaction = rule.reaction
|
||||
for name, content in result.groupdict().items():
|
||||
reaction = reaction.replace(f"[{name}]", content)
|
||||
logger.info(f"匹配到正则表达式:{pattern_str},触发反应:{reaction}")
|
||||
keywords_reaction_prompt += f"{reaction},"
|
||||
break
|
||||
except re.error as e:
|
||||
logger.error(f"正则表达式编译错误: {pattern_str}, 错误信息: {e!s}")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.error(f"关键词检测与反应时发生异常: {e!s}", exc_info=True)
|
||||
|
||||
return keywords_reaction_prompt
|
||||
return reaction_prompt
|
||||
|
||||
async def build_notice_block(self, chat_id: str) -> str:
|
||||
"""构建notice信息块
|
||||
|
||||
@@ -26,7 +26,7 @@ from src.config.official_configs import (
|
||||
EmojiConfig,
|
||||
ExperimentalConfig,
|
||||
ExpressionConfig,
|
||||
KeywordReactionConfig,
|
||||
ReactionConfig,
|
||||
LPMMKnowledgeConfig,
|
||||
MaimMessageConfig,
|
||||
MemoryConfig,
|
||||
@@ -384,7 +384,7 @@ class Config(ValidatedConfigBase):
|
||||
expression: ExpressionConfig = Field(..., description="表达配置")
|
||||
memory: MemoryConfig = Field(..., description="记忆配置")
|
||||
mood: MoodConfig = Field(..., description="情绪配置")
|
||||
keyword_reaction: KeywordReactionConfig = Field(..., description="关键词反应配置")
|
||||
reaction: ReactionConfig = Field(default_factory=ReactionConfig, description="反应规则配置")
|
||||
chinese_typo: ChineseTypoConfig = Field(..., description="中文错别字配置")
|
||||
response_post_process: ResponsePostProcessConfig = Field(..., description="响应后处理配置")
|
||||
response_splitter: ResponseSplitterConfig = Field(..., description="响应分割配置")
|
||||
|
||||
@@ -401,32 +401,31 @@ class MoodConfig(ValidatedConfigBase):
|
||||
mood_update_threshold: float = Field(default=1.0, description="情绪更新阈值")
|
||||
|
||||
|
||||
class KeywordRuleConfig(ValidatedConfigBase):
|
||||
"""关键词规则配置类"""
|
||||
class ReactionRuleConfig(ValidatedConfigBase):
|
||||
"""反应规则配置类"""
|
||||
|
||||
keywords: list[str] = Field(default_factory=lambda: [], description="关键词列表")
|
||||
regex: list[str] = Field(default_factory=lambda: [], description="正则表达式列表")
|
||||
reaction: str = Field(default="", description="反应内容")
|
||||
chat_stream_id: str = Field(default="", description='聊天流ID,格式为 "platform:id:type",空字符串表示全局')
|
||||
rule_type: Literal["keyword", "regex"] = Field(..., description='规则类型,必须是 "keyword" 或 "regex"')
|
||||
patterns: list[str] = Field(..., description="关键词或正则表达式列表")
|
||||
reaction: str = Field(..., description="触发后的回复内容")
|
||||
|
||||
def __post_init__(self):
|
||||
import re
|
||||
|
||||
if not self.keywords and not self.regex:
|
||||
raise ValueError("关键词规则必须至少包含keywords或regex中的一个")
|
||||
if not self.reaction:
|
||||
raise ValueError("关键词规则必须包含reaction")
|
||||
for pattern in self.regex:
|
||||
try:
|
||||
re.compile(pattern)
|
||||
except re.error as e:
|
||||
raise ValueError(f"无效的正则表达式 '{pattern}': {e!s}") from e
|
||||
if not self.patterns:
|
||||
raise ValueError("patterns 列表不能为空")
|
||||
if self.rule_type == "regex":
|
||||
for pattern in self.patterns:
|
||||
try:
|
||||
re.compile(pattern)
|
||||
except re.error as e:
|
||||
raise ValueError(f"无效的正则表达式 '{pattern}': {e!s}") from e
|
||||
|
||||
|
||||
class KeywordReactionConfig(ValidatedConfigBase):
|
||||
"""关键词配置类"""
|
||||
class ReactionConfig(ValidatedConfigBase):
|
||||
"""反应规则系统配置"""
|
||||
|
||||
keyword_rules: list[KeywordRuleConfig] = Field(default_factory=lambda: [], description="关键词规则列表")
|
||||
regex_rules: list[KeywordRuleConfig] = Field(default_factory=lambda: [], description="正则表达式规则列表")
|
||||
rules: list[ReactionRuleConfig] = Field(default_factory=list, description="反应规则列表")
|
||||
|
||||
|
||||
class CustomPromptConfig(ValidatedConfigBase):
|
||||
|
||||
Reference in New Issue
Block a user