feat: 在插件系统中添加 Chatter 组件

- 在 component_types.py 中新增了用于 CHATTER 的 ComponentType。
- 实现了 ChatterInfo 类,用于存储 Chatter 组件的相关信息。
- 增强了 ComponentRegistry,以支持 Chatter 组件的注册与管理。
- 创建了 ChatterManager,用于管理 Chatter 实例并处理聊天流。
- 开发了 BaseChatter 抽象类,用于定义 Chatter 的行为规范。
- 实现了 AffinityChatter,作为具备兴趣评分与关系构建功能的具体 Chatter 组件。
- 添加了一个内置的 Chatter 插件,并附带完整文档与使用示例。
- 更新了 PluginManager,在插件概览中加入 Chatter 组件的统计信息。
This commit is contained in:
Windpicker-owo
2025-09-23 00:17:32 +08:00
parent e382b2ffd9
commit e1683ee9e6
15 changed files with 806 additions and 60 deletions

View File

@@ -3,6 +3,8 @@
提供全局的AFC管理器实例
"""
from src.chat.affinity_flow.afc_manager import afc_manager
# Avoid importing submodules at package import time to prevent circular imports.
# Consumers should import specific submodules directly, for example:
# from src.chat.affinity_flow.afc_manager import afc_manager
__all__ = ["afc_manager", "AFCManager", "AffinityFlowChatter"]

View File

@@ -11,6 +11,7 @@ from typing import Dict
from src.chat.planner_actions.action_manager import ActionManager
from src.chat.planner_actions.planner import ActionPlanner
from src.common.data_models.message_manager_data_model import StreamContext
from src.plugin_system.base.base_chatter import BaseChatter
from src.plugin_system.base.component_types import ChatMode
from src.common.logger import get_logger
@@ -18,7 +19,7 @@ from src.common.logger import get_logger
logger = get_logger("affinity_chatter")
class AffinityFlowChatter:
class AffinityFlowChatter(BaseChatter):
"""单个亲和力聊天处理器"""
def __init__(self, stream_id: str, planner: ActionPlanner, action_manager: ActionManager):
@@ -44,7 +45,7 @@ class AffinityFlowChatter:
}
self.last_activity_time = time.time()
async def process_stream_context(self, context: StreamContext) -> Dict[str, any]:
async def execute(self, context: StreamContext) -> dict:
"""
处理StreamContext对象

136
src/chat/chatter_manager.py Normal file
View File

@@ -0,0 +1,136 @@
from typing import Dict, List, Optional, Any
import time
from src.plugin_system.base.base_chatter import BaseChatter
from src.common.data_models.message_manager_data_model import StreamContext
from src.chat.planner_actions.planner import ActionPlanner
from src.chat.planner_actions.action_manager import ActionManager
from src.plugin_system.base.component_types import ChatType, ComponentType
from src.common.logger import get_logger
logger = get_logger("chatter_manager")
class ChatterManager:
def __init__(self, action_manager: ActionManager):
self.action_manager = action_manager
self.chatter_classes: Dict[ChatType, List[type]] = {}
self.instances: Dict[str, BaseChatter] = {}
# 管理器统计
self.stats = {
"chatters_registered": 0,
"streams_processed": 0,
"successful_executions": 0,
"failed_executions": 0,
}
def _auto_register_from_component_registry(self):
"""从组件注册表自动注册已注册的chatter组件"""
try:
from src.plugin_system.core.component_registry import component_registry
# 获取所有CHATTER类型的组件
chatter_components = component_registry.get_enabled_chatter_registry()
for chatter_name, chatter_class in chatter_components.items():
self.register_chatter(chatter_class)
logger.info(f"自动注册chatter组件: {chatter_name}")
except Exception as e:
logger.warning(f"自动注册chatter组件时发生错误: {e}")
def register_chatter(self, chatter_class: type):
"""注册聊天处理器类"""
for chat_type in chatter_class.chat_types:
if chat_type not in self.chatter_classes:
self.chatter_classes[chat_type] = []
self.chatter_classes[chat_type].append(chatter_class)
logger.info(f"注册聊天处理器 {chatter_class.__name__} 支持 {chat_type.value} 聊天类型")
self.stats["chatters_registered"] += 1
def get_chatter_class(self, chat_type: ChatType) -> Optional[type]:
"""获取指定聊天类型的聊天处理器类"""
if chat_type in self.chatter_classes:
return self.chatter_classes[chat_type][0]
return None
def get_supported_chat_types(self) -> List[ChatType]:
"""获取支持的聊天类型列表"""
return list(self.chatter_classes.keys())
def get_registered_chatters(self) -> Dict[ChatType, List[type]]:
"""获取已注册的聊天处理器"""
return self.chatter_classes.copy()
def get_stream_instance(self, stream_id: str) -> Optional[BaseChatter]:
"""获取指定流的聊天处理器实例"""
return self.instances.get(stream_id)
def cleanup_inactive_instances(self, max_inactive_minutes: int = 60):
"""清理不活跃的实例"""
current_time = time.time()
max_inactive_seconds = max_inactive_minutes * 60
inactive_streams = []
for stream_id, instance in self.instances.items():
if hasattr(instance, 'get_activity_time'):
activity_time = instance.get_activity_time()
if (current_time - activity_time) > max_inactive_seconds:
inactive_streams.append(stream_id)
for stream_id in inactive_streams:
del self.instances[stream_id]
logger.info(f"清理不活跃聊天流实例: {stream_id}")
async def process_stream_context(self, stream_id: str, context: StreamContext) -> dict:
"""处理流上下文"""
chat_type = context.chat_type
logger.debug(f"处理流 {stream_id},聊天类型: {chat_type.value}")
if not self.chatter_classes:
self._auto_register_from_component_registry()
# 获取适合该聊天类型的chatter
chatter_class = self.get_chatter_class(chat_type)
if not chatter_class:
# 如果没有找到精确匹配尝试查找支持ALL类型的chatter
from src.plugin_system.base.component_types import ChatType
all_chatter_class = self.get_chatter_class(ChatType.ALL)
if all_chatter_class:
chatter_class = all_chatter_class
logger.info(f"{stream_id} 使用通用chatter (类型: {chat_type.value})")
else:
raise ValueError(f"No chatter registered for chat type {chat_type}")
if stream_id not in self.instances:
planner = ActionPlanner(stream_id, self.action_manager)
self.instances[stream_id] = chatter_class(stream_id=stream_id, planner=planner, action_manager=self.action_manager)
logger.info(f"创建新的聊天流实例: {stream_id} 使用 {chatter_class.__name__} (类型: {chat_type.value})")
self.stats["streams_processed"] += 1
try:
result = await self.instances[stream_id].execute(context)
self.stats["successful_executions"] += 1
# 记录处理结果
success = result.get("success", False)
actions_count = result.get("actions_count", 0)
logger.debug(f"{stream_id} 处理完成: 成功={success}, 动作数={actions_count}")
return result
except Exception as e:
self.stats["failed_executions"] += 1
logger.error(f"处理流 {stream_id} 时发生错误: {e}")
raise
def get_stats(self) -> Dict[str, Any]:
"""获取管理器统计信息"""
stats = self.stats.copy()
stats["active_instances"] = len(self.instances)
stats["registered_chatter_types"] = len(self.chatter_classes)
return stats
def reset_stats(self):
"""重置统计信息"""
self.stats = {
"chatters_registered": 0,
"streams_processed": 0,
"successful_executions": 0,
"failed_executions": 0,
}

View File

@@ -11,7 +11,8 @@ from typing import Dict, Optional, Any, TYPE_CHECKING
from src.common.logger import get_logger
from src.common.data_models.database_data_model import DatabaseMessages
from src.common.data_models.message_manager_data_model import StreamContext, MessageManagerStats, StreamStats
from src.chat.affinity_flow.afc_manager import afc_manager
from src.chat.chatter_manager import ChatterManager
from src.chat.planner_actions.action_manager import ActionManager
if TYPE_CHECKING:
from src.common.data_models.message_manager_data_model import StreamContext
@@ -31,6 +32,10 @@ class MessageManager:
# 统计信息
self.stats = MessageManagerStats()
# 初始化chatter manager
self.action_manager = ActionManager()
self.chatter_manager = ChatterManager(self.action_manager)
async def start(self):
"""启动消息管理器"""
if self.is_running:
@@ -125,15 +130,23 @@ class MessageManager:
# 直接使用StreamContext对象进行处理
if unread_messages:
try:
# 发送到AFC处理器传递StreamContext对象
results = await afc_manager.process_stream_context(stream_id, context)
# 记录当前chat type用于调试
logger.debug(f"聊天流 {stream_id} 检测到的chat type: {context.chat_type.value}")
# 发送到chatter manager传递StreamContext对象
results = await self.chatter_manager.process_stream_context(stream_id, context)
# 处理结果,标记消息为已读
if results.get("success", False):
self._clear_all_unread_messages(context)
logger.debug(f"聊天流 {stream_id} 处理成功,清除了 {len(unread_messages)} 条未读消息")
else:
logger.warning(f"聊天流 {stream_id} 处理失败: {results.get('error_message', '未知错误')}")
except Exception as e:
logger.error(f"处理聊天流 {stream_id} 时发生异常,将清除所有未读消息: {e}")
# 出现异常时也清除未读消息,避免重复处理
self._clear_all_unread_messages(context)
raise
logger.debug(f"聊天流 {stream_id} 消息处理完成")

View File

@@ -4,19 +4,23 @@
"""
from dataclasses import asdict
from typing import Dict, List, Optional, Tuple
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
from src.chat.planner_actions.action_manager import ActionManager
from src.plugin_system.base.component_types import ChatMode
from src.chat.planner_actions.plan_executor import PlanExecutor
from src.chat.planner_actions.plan_filter import PlanFilter
from src.chat.planner_actions.plan_generator import PlanGenerator
from src.chat.affinity_flow.interest_scoring import InterestScoringSystem
from src.chat.affinity_flow.relationship_tracker import UserRelationshipTracker
from src.common.data_models.info_data_model import Plan
from src.common.data_models.message_manager_data_model import StreamContext
from src.common.logger import get_logger
from src.config.config import global_config
from src.plugin_system.base.component_types import ChatMode
if TYPE_CHECKING:
from src.chat.planner_actions.action_manager import ActionManager
from src.common.data_models.message_manager_data_model import StreamContext
from src.common.data_models.info_data_model import Plan
# 导入提示词模块以确保其被初始化
from src.chat.planner_actions import planner_prompts # noqa
@@ -35,7 +39,7 @@ class ActionPlanner:
4. 完整的规划流程:生成→筛选→执行的完整三阶段流程
"""
def __init__(self, chat_id: str, action_manager: ActionManager):
def __init__(self, chat_id: str, action_manager: "ActionManager"):
"""
初始化增强版ActionPlanner。
@@ -85,7 +89,7 @@ class ActionPlanner:
}
async def plan(
self, mode: ChatMode = ChatMode.FOCUS, context: StreamContext = None
self, mode: ChatMode = ChatMode.FOCUS, context: "StreamContext" = None
) -> Tuple[List[Dict], Optional[Dict]]:
"""
执行完整的增强版规划流程。
@@ -109,7 +113,7 @@ class ActionPlanner:
self.planner_stats["failed_plans"] += 1
return [], None
async def _enhanced_plan_flow(self, mode: ChatMode, context: StreamContext) -> Tuple[List[Dict], Optional[Dict]]:
async def _enhanced_plan_flow(self, mode: ChatMode, context: "StreamContext") -> Tuple[List[Dict], Optional[Dict]]:
"""执行增强版规划流程"""
try:
# 1. 生成初始 Plan
@@ -204,7 +208,7 @@ class ActionPlanner:
self.planner_stats["replies_generated"] += reply_count
self.planner_stats["other_actions_executed"] += other_count
def _build_return_result(self, plan: Plan) -> Tuple[List[Dict], Optional[Dict]]:
def _build_return_result(self, plan: "Plan") -> Tuple[List[Dict], Optional[Dict]]:
"""构建返回结果"""
final_actions = plan.decided_actions or []
final_target_message = next((act.action_message for act in final_actions if act.action_message), None)

View File

@@ -10,6 +10,7 @@ from enum import Enum
from typing import List, Optional, TYPE_CHECKING
from . import BaseDataModel
from src.plugin_system.base.component_types import ChatType
if TYPE_CHECKING:
from .database_data_model import DatabaseMessages
@@ -28,6 +29,7 @@ class StreamContext(BaseDataModel):
"""聊天流上下文信息"""
stream_id: str
chat_type: ChatType = ChatType.PRIVATE # 聊天类型,默认为私聊
unread_messages: List["DatabaseMessages"] = field(default_factory=list)
history_messages: List["DatabaseMessages"] = field(default_factory=list)
last_check_time: float = field(default_factory=time.time)
@@ -39,6 +41,42 @@ class StreamContext(BaseDataModel):
message.is_read = False
self.unread_messages.append(message)
# 自动检测和更新chat type
self._detect_chat_type(message)
def _detect_chat_type(self, message: "DatabaseMessages"):
"""根据消息内容自动检测聊天类型"""
# 只有在第一次添加消息时才检测聊天类型,避免后续消息改变类型
if len(self.unread_messages) == 1: # 只有这条消息
# 如果消息包含群组信息,则为群聊
if hasattr(message, 'chat_info_group_id') and message.chat_info_group_id:
self.chat_type = ChatType.GROUP
elif hasattr(message, 'chat_info_group_name') and message.chat_info_group_name:
self.chat_type = ChatType.GROUP
else:
self.chat_type = ChatType.PRIVATE
def update_chat_type(self, chat_type: ChatType):
"""手动更新聊天类型"""
self.chat_type = chat_type
def is_group_chat(self) -> bool:
"""检查是否为群聊"""
return self.chat_type == ChatType.GROUP
def is_private_chat(self) -> bool:
"""检查是否为私聊"""
return self.chat_type == ChatType.PRIVATE
def get_chat_type_display(self) -> str:
"""获取聊天类型的显示名称"""
if self.chat_type == ChatType.GROUP:
return "群聊"
elif self.chat_type == ChatType.PRIVATE:
return "私聊"
else:
return "未知类型"
def get_unread_messages(self) -> List["DatabaseMessages"]:
"""获取未读消息"""
return [msg for msg in self.unread_messages if not msg.is_read]

View File

@@ -0,0 +1,57 @@
from abc import ABC, abstractmethod
from typing import List, Optional, TYPE_CHECKING
from src.common.data_models.message_manager_data_model import StreamContext
from .component_types import ChatType
from src.plugin_system.base.component_types import ChatterInfo, ComponentType
if TYPE_CHECKING:
from src.chat.planner_actions.action_manager import ActionManager
from src.chat.planner_actions.planner import ActionPlanner
class BaseChatter(ABC):
chatter_name: str = ""
"""Chatter组件的名称"""
chatter_description: str = ""
"""Chatter组件的描述"""
chat_types: List[ChatType] = [ChatType.PRIVATE, ChatType.GROUP]
def __init__(self, stream_id: str, planner: 'ActionPlanner', action_manager: 'ActionManager'):
"""
初始化聊天处理器
Args:
stream_id: 聊天流ID
planner: 动作规划器
action_manager: 动作管理器
"""
self.stream_id = stream_id
self.planner = planner
self.action_manager = action_manager
@abstractmethod
async def execute(self, context: StreamContext) -> dict:
"""
执行聊天处理流程
Args:
context: StreamContext对象包含聊天流的所有消息信息
Returns:
处理结果字典
"""
pass
@classmethod
def get_chatter_info(cls) -> "ChatterInfo":
"""从类属性生成ChatterInfo
Returns:
ChatterInfo对象
"""
return ChatterInfo(
name=cls.chatter_name,
description=cls.chatter_description or "No description provided.",
chat_type_allow=cls.chat_types[0],
component_type=ComponentType.CHATTER,
)

View File

@@ -17,6 +17,7 @@ class ComponentType(Enum):
TOOL = "tool" # 工具组件
SCHEDULER = "scheduler" # 定时任务组件(预留)
EVENT_HANDLER = "event_handler" # 事件处理组件
CHATTER = "chatter" # 聊天处理器组件
def __str__(self) -> str:
return self.value
@@ -54,8 +55,8 @@ class ChatMode(Enum):
class ChatType(Enum):
"""聊天类型枚举,用于限制插件在不同聊天环境中的使用"""
GROUP = "group" # 仅群聊可用
PRIVATE = "private" # 仅私聊可用
GROUP = "group" # 仅群聊可用
ALL = "all" # 群聊和私聊都可用
def __str__(self):
@@ -210,6 +211,17 @@ class EventHandlerInfo(ComponentInfo):
self.component_type = ComponentType.EVENT_HANDLER
@dataclass
class ChatterInfo(ComponentInfo):
"""聊天处理器组件信息"""
chat_type_allow: ChatType = ChatType.ALL # 允许的聊天类型
def __post_init__(self):
super().__post_init__()
self.component_type = ComponentType.CHATTER
@dataclass
class EventInfo(ComponentInfo):
"""事件组件信息"""

View File

@@ -1,7 +1,7 @@
from pathlib import Path
import re
from typing import Dict, List, Optional, Any, Pattern, Tuple, Union, Type
from typing import TYPE_CHECKING, Dict, List, Optional, Any, Pattern, Tuple, Union, Type
from src.common.logger import get_logger
from src.plugin_system.base.component_types import (
@@ -11,14 +11,17 @@ from src.plugin_system.base.component_types import (
CommandInfo,
PlusCommandInfo,
EventHandlerInfo,
ChatterInfo,
PluginInfo,
ComponentType,
)
from src.plugin_system.base.base_command import BaseCommand
from src.plugin_system.base.base_action import BaseAction
from src.plugin_system.base.base_tool import BaseTool
from src.plugin_system.base.base_events_handler import BaseEventHandler
from src.plugin_system.base.plus_command import PlusCommand
from src.plugin_system.base.base_chatter import BaseChatter
logger = get_logger("component_registry")
@@ -31,41 +34,45 @@ class ComponentRegistry:
def __init__(self):
# 命名空间式组件名构成法 f"{component_type}.{component_name}"
self._components: Dict[str, ComponentInfo] = {}
self._components: Dict[str, 'ComponentInfo'] = {}
"""组件注册表 命名空间式组件名 -> 组件信息"""
self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType}
self._components_by_type: Dict['ComponentType', Dict[str, 'ComponentInfo']] = {types: {} for types in ComponentType}
"""类型 -> 组件原名称 -> 组件信息"""
self._components_classes: Dict[
str, Type[Union[BaseCommand, BaseAction, BaseTool, BaseEventHandler, PlusCommand]]
str, Type[Union['BaseCommand', 'BaseAction', 'BaseTool', 'BaseEventHandler', 'PlusCommand', 'BaseChatter']]
] = {}
"""命名空间式组件名 -> 组件类"""
# 插件注册表
self._plugins: Dict[str, PluginInfo] = {}
self._plugins: Dict[str, 'PluginInfo'] = {}
"""插件名 -> 插件信息"""
# Action特定注册表
self._action_registry: Dict[str, Type[BaseAction]] = {}
self._action_registry: Dict[str, Type['BaseAction']] = {}
"""Action注册表 action名 -> action类"""
self._default_actions: Dict[str, ActionInfo] = {}
self._default_actions: Dict[str, 'ActionInfo'] = {}
"""默认动作集即启用的Action集用于重置ActionManager状态"""
# Command特定注册表
self._command_registry: Dict[str, Type[BaseCommand]] = {}
self._command_registry: Dict[str, Type['BaseCommand']] = {}
"""Command类注册表 command名 -> command类"""
self._command_patterns: Dict[Pattern, str] = {}
"""编译后的正则 -> command名"""
# 工具特定注册表
self._tool_registry: Dict[str, Type[BaseTool]] = {} # 工具名 -> 工具类
self._llm_available_tools: Dict[str, Type[BaseTool]] = {} # llm可用的工具名 -> 工具类
self._tool_registry: Dict[str, Type['BaseTool']] = {} # 工具名 -> 工具类
self._llm_available_tools: Dict[str, Type['BaseTool']] = {} # llm可用的工具名 -> 工具类
# EventHandler特定注册表
self._event_handler_registry: Dict[str, Type[BaseEventHandler]] = {}
self._event_handler_registry: Dict[str, Type['BaseEventHandler']] = {}
"""event_handler名 -> event_handler类"""
self._enabled_event_handlers: Dict[str, Type[BaseEventHandler]] = {}
self._enabled_event_handlers: Dict[str, Type['BaseEventHandler']] = {}
"""启用的事件处理器 event_handler名 -> event_handler类"""
self._chatter_registry: Dict[str, Type['BaseChatter']] = {}
"""chatter名 -> chatter类"""
self._enabled_chatter_registry: Dict[str, Type['BaseChatter']] = {}
"""启用的chatter名 -> chatter类"""
logger.info("组件注册中心初始化完成")
# == 注册方法 ==
@@ -92,7 +99,7 @@ class ComponentRegistry:
def register_component(
self,
component_info: ComponentInfo,
component_class: Type[Union[BaseCommand, BaseAction, BaseEventHandler, BaseTool]],
component_class: Type[Union['BaseCommand', 'BaseAction', 'BaseEventHandler', 'BaseTool', 'BaseChatter']],
) -> bool:
"""注册组件
@@ -150,6 +157,10 @@ class ComponentRegistry:
assert isinstance(component_info, EventHandlerInfo)
assert issubclass(component_class, BaseEventHandler)
ret = self._register_event_handler_component(component_info, component_class)
case ComponentType.CHATTER:
assert isinstance(component_info, ChatterInfo)
assert issubclass(component_class, BaseChatter)
ret = self._register_chatter_component(component_info, component_class)
case _:
logger.warning(f"未知组件类型: {component_type}")
@@ -161,7 +172,7 @@ class ComponentRegistry:
)
return True
def _register_action_component(self, action_info: ActionInfo, action_class: Type[BaseAction]) -> bool:
def _register_action_component(self, action_info: 'ActionInfo', action_class: Type['BaseAction']) -> bool:
"""注册Action组件到Action特定注册表"""
if not (action_name := action_info.name):
logger.error(f"Action组件 {action_class.__name__} 必须指定名称")
@@ -181,7 +192,7 @@ class ComponentRegistry:
return True
def _register_command_component(self, command_info: CommandInfo, command_class: Type[BaseCommand]) -> bool:
def _register_command_component(self, command_info: 'CommandInfo', command_class: Type['BaseCommand']) -> bool:
"""注册Command组件到Command特定注册表"""
if not (command_name := command_info.name):
logger.error(f"Command组件 {command_class.__name__} 必须指定名称")
@@ -208,7 +219,7 @@ class ComponentRegistry:
return True
def _register_plus_command_component(
self, plus_command_info: PlusCommandInfo, plus_command_class: Type[PlusCommand]
self, plus_command_info: 'PlusCommandInfo', plus_command_class: Type['PlusCommand']
) -> bool:
"""注册PlusCommand组件到特定注册表"""
plus_command_name = plus_command_info.name
@@ -222,7 +233,7 @@ class ComponentRegistry:
# 创建专门的PlusCommand注册表如果还没有
if not hasattr(self, "_plus_command_registry"):
self._plus_command_registry: Dict[str, Type[PlusCommand]] = {}
self._plus_command_registry: Dict[str, Type['PlusCommand']] = {}
plus_command_class.plugin_name = plus_command_info.plugin_name
# 设置插件配置
@@ -232,7 +243,7 @@ class ComponentRegistry:
logger.debug(f"已注册PlusCommand组件: {plus_command_name}")
return True
def _register_tool_component(self, tool_info: ToolInfo, tool_class: Type[BaseTool]) -> bool:
def _register_tool_component(self, tool_info: 'ToolInfo', tool_class: Type['BaseTool']) -> bool:
"""注册Tool组件到Tool特定注册表"""
tool_name = tool_info.name
@@ -248,7 +259,7 @@ class ComponentRegistry:
return True
def _register_event_handler_component(
self, handler_info: EventHandlerInfo, handler_class: Type[BaseEventHandler]
self, handler_info: 'EventHandlerInfo', handler_class: Type['BaseEventHandler']
) -> bool:
if not (handler_name := handler_info.name):
logger.error(f"EventHandler组件 {handler_class.__name__} 必须指定名称")
@@ -274,9 +285,34 @@ class ComponentRegistry:
handler_class, self.get_plugin_config(handler_info.plugin_name) or {}
)
def _register_chatter_component(self, chatter_info: 'ChatterInfo', chatter_class: Type['BaseChatter']) -> bool:
"""注册Chatter组件到Chatter特定注册表"""
chatter_name = chatter_info.name
if not chatter_name:
logger.error(f"Chatter组件 {chatter_class.__name__} 必须指定名称")
return False
if not isinstance(chatter_info, ChatterInfo) or not issubclass(chatter_class, BaseChatter):
logger.error(f"注册失败: {chatter_name} 不是有效的Chatter")
return False
chatter_class.plugin_name = chatter_info.plugin_name
# 设置插件配置
chatter_class.plugin_config = self.get_plugin_config(chatter_info.plugin_name) or {}
self._chatter_registry[chatter_name] = chatter_class
if not chatter_info.enabled:
logger.warning(f"Chatter组件 {chatter_name} 未启用")
return True # 未启用,但是也是注册成功
self._enabled_chatter_registry[chatter_name] = chatter_class
logger.debug(f"已注册Chatter组件: {chatter_name}")
return True
# === 组件移除相关 ===
async def remove_component(self, component_name: str, component_type: ComponentType, plugin_name: str) -> bool:
async def remove_component(self, component_name: str, component_type: 'ComponentType', plugin_name: str) -> bool:
target_component_class = self.get_component_class(component_name, component_type)
if not target_component_class:
logger.warning(f"组件 {component_name} 未注册,无法移除")
@@ -324,6 +360,12 @@ class ComponentRegistry:
except Exception as e:
logger.warning(f"移除EventHandler事件订阅时出错: {e}")
case ComponentType.CHATTER:
# 移除Chatter注册
if hasattr(self, '_chatter_registry'):
self._chatter_registry.pop(component_name, None)
logger.debug(f"已移除Chatter组件: {component_name}")
case _:
logger.warning(f"未知的组件类型: {component_type}")
return False
@@ -442,8 +484,8 @@ class ComponentRegistry:
# === 组件查询方法 ===
def get_component_info(
self, component_name: str, component_type: Optional[ComponentType] = None
) -> Optional[ComponentInfo]:
self, component_name: str, component_type: Optional['ComponentType'] = None
) -> Optional['ComponentInfo']:
# sourcery skip: class-extract-method
"""获取组件信息,支持自动命名空间解析
@@ -487,8 +529,8 @@ class ComponentRegistry:
def get_component_class(
self,
component_name: str,
component_type: Optional[ComponentType] = None,
) -> Optional[Union[Type[BaseCommand], Type[BaseAction], Type[BaseEventHandler], Type[BaseTool]]]:
component_type: Optional['ComponentType'] = None,
) -> Optional[Union[Type['BaseCommand'], Type['BaseAction'], Type['BaseEventHandler'], Type['BaseTool']]]:
"""获取组件类,支持自动命名空间解析
Args:
@@ -505,7 +547,7 @@ class ComponentRegistry:
# 2. 如果指定了组件类型,构造命名空间化的名称查找
if component_type:
namespaced_name = f"{component_type.value}.{component_name}"
return self._components_classes.get(namespaced_name)
return self._components_classes.get(namespaced_name) # type: ignore[valid-type]
# 3. 如果没有指定类型,尝试在所有命名空间中查找
candidates = []
@@ -530,22 +572,22 @@ class ComponentRegistry:
# 4. 都没找到
return None
def get_components_by_type(self, component_type: ComponentType) -> Dict[str, ComponentInfo]:
def get_components_by_type(self, component_type: 'ComponentType') -> Dict[str, 'ComponentInfo']:
"""获取指定类型的所有组件"""
return self._components_by_type.get(component_type, {}).copy()
def get_enabled_components_by_type(self, component_type: ComponentType) -> Dict[str, ComponentInfo]:
def get_enabled_components_by_type(self, component_type: 'ComponentType') -> Dict[str, 'ComponentInfo']:
"""获取指定类型的所有启用组件"""
components = self.get_components_by_type(component_type)
return {name: info for name, info in components.items() if info.enabled}
# === Action特定查询方法 ===
def get_action_registry(self) -> Dict[str, Type[BaseAction]]:
def get_action_registry(self) -> Dict[str, Type['BaseAction']]:
"""获取Action注册表"""
return self._action_registry.copy()
def get_registered_action_info(self, action_name: str) -> Optional[ActionInfo]:
def get_registered_action_info(self, action_name: str) -> Optional['ActionInfo']:
"""获取Action信息"""
info = self.get_component_info(action_name, ComponentType.ACTION)
return info if isinstance(info, ActionInfo) else None
@@ -556,11 +598,11 @@ class ComponentRegistry:
# === Command特定查询方法 ===
def get_command_registry(self) -> Dict[str, Type[BaseCommand]]:
def get_command_registry(self) -> Dict[str, Type['BaseCommand']]:
"""获取Command注册表"""
return self._command_registry.copy()
def get_registered_command_info(self, command_name: str) -> Optional[CommandInfo]:
def get_registered_command_info(self, command_name: str) -> Optional['CommandInfo']:
"""获取Command信息"""
info = self.get_component_info(command_name, ComponentType.COMMAND)
return info if isinstance(info, CommandInfo) else None
@@ -569,7 +611,7 @@ class ComponentRegistry:
"""获取Command模式注册表"""
return self._command_patterns.copy()
def find_command_by_text(self, text: str) -> Optional[Tuple[Type[BaseCommand], dict, CommandInfo]]:
def find_command_by_text(self, text: str) -> Optional[Tuple[Type['BaseCommand'], dict, 'CommandInfo']]:
# sourcery skip: use-named-expression, use-next
"""根据文本查找匹配的命令
@@ -596,15 +638,15 @@ class ComponentRegistry:
return None
# === Tool 特定查询方法 ===
def get_tool_registry(self) -> Dict[str, Type[BaseTool]]:
def get_tool_registry(self) -> Dict[str, Type['BaseTool']]:
"""获取Tool注册表"""
return self._tool_registry.copy()
def get_llm_available_tools(self) -> Dict[str, Type[BaseTool]]:
def get_llm_available_tools(self) -> Dict[str, Type['BaseTool']]:
"""获取LLM可用的Tool列表"""
return self._llm_available_tools.copy()
def get_registered_tool_info(self, tool_name: str) -> Optional[ToolInfo]:
def get_registered_tool_info(self, tool_name: str) -> Optional['ToolInfo']:
"""获取Tool信息
Args:
@@ -617,13 +659,13 @@ class ComponentRegistry:
return info if isinstance(info, ToolInfo) else None
# === PlusCommand 特定查询方法 ===
def get_plus_command_registry(self) -> Dict[str, Type[PlusCommand]]:
def get_plus_command_registry(self) -> Dict[str, Type['PlusCommand']]:
"""获取PlusCommand注册表"""
if not hasattr(self, "_plus_command_registry"):
self._plus_command_registry: Dict[str, Type[PlusCommand]] = {}
return self._plus_command_registry.copy()
def get_registered_plus_command_info(self, command_name: str) -> Optional[PlusCommandInfo]:
def get_registered_plus_command_info(self, command_name: str) -> Optional['PlusCommandInfo']:
"""获取PlusCommand信息
Args:
@@ -637,26 +679,44 @@ class ComponentRegistry:
# === EventHandler 特定查询方法 ===
def get_event_handler_registry(self) -> Dict[str, Type[BaseEventHandler]]:
def get_event_handler_registry(self) -> Dict[str, Type['BaseEventHandler']]:
"""获取事件处理器注册表"""
return self._event_handler_registry.copy()
def get_registered_event_handler_info(self, handler_name: str) -> Optional[EventHandlerInfo]:
def get_registered_event_handler_info(self, handler_name: str) -> Optional['EventHandlerInfo']:
"""获取事件处理器信息"""
info = self.get_component_info(handler_name, ComponentType.EVENT_HANDLER)
return info if isinstance(info, EventHandlerInfo) else None
def get_enabled_event_handlers(self) -> Dict[str, Type[BaseEventHandler]]:
def get_enabled_event_handlers(self) -> Dict[str, Type['BaseEventHandler']]:
"""获取启用的事件处理器"""
return self._enabled_event_handlers.copy()
# === Chatter 特定查询方法 ===
def get_chatter_registry(self) -> Dict[str, Type['BaseChatter']]:
"""获取Chatter注册表"""
if not hasattr(self, '_chatter_registry'):
self._chatter_registry: Dict[str, Type[BaseChatter]] = {}
return self._chatter_registry.copy()
def get_enabled_chatter_registry(self) -> Dict[str, Type['BaseChatter']]:
"""获取启用的Chatter注册表"""
if not hasattr(self, '_enabled_chatter_registry'):
self._enabled_chatter_registry: Dict[str, Type[BaseChatter]] = {}
return self._enabled_chatter_registry.copy()
def get_registered_chatter_info(self, chatter_name: str) -> Optional['ChatterInfo']:
"""获取Chatter信息"""
info = self.get_component_info(chatter_name, ComponentType.CHATTER)
return info if isinstance(info, ChatterInfo) else None
# === 插件查询方法 ===
def get_plugin_info(self, plugin_name: str) -> Optional[PluginInfo]:
def get_plugin_info(self, plugin_name: str) -> Optional['PluginInfo']:
"""获取插件信息"""
return self._plugins.get(plugin_name)
def get_all_plugins(self) -> Dict[str, PluginInfo]:
def get_all_plugins(self) -> Dict[str, 'PluginInfo']:
"""获取所有插件"""
return self._plugins.copy()
@@ -664,7 +724,7 @@ class ComponentRegistry:
# """获取所有启用的插件"""
# return {name: info for name, info in self._plugins.items() if info.enabled}
def get_plugin_components(self, plugin_name: str) -> List[ComponentInfo]:
def get_plugin_components(self, plugin_name: str) -> List['ComponentInfo']:
"""获取插件的所有组件"""
plugin_info = self.get_plugin_info(plugin_name)
return plugin_info.components if plugin_info else []
@@ -707,6 +767,7 @@ class ComponentRegistry:
tool_components: int = 0
events_handlers: int = 0
plus_command_components: int = 0
chatter_components: int = 0
for component in self._components.values():
if component.component_type == ComponentType.ACTION:
action_components += 1
@@ -718,12 +779,15 @@ class ComponentRegistry:
events_handlers += 1
elif component.component_type == ComponentType.PLUS_COMMAND:
plus_command_components += 1
elif component.component_type == ComponentType.CHATTER:
chatter_components += 1
return {
"action_components": action_components,
"command_components": command_components,
"tool_components": tool_components,
"event_handlers": events_handlers,
"plus_command_components": plus_command_components,
"chatter_components": chatter_components,
"total_components": len(self._components),
"total_plugins": len(self._plugins),
"components_by_type": {
@@ -732,6 +796,10 @@ class ComponentRegistry:
"enabled_components": len([c for c in self._components.values() if c.enabled]),
"enabled_plugins": len([p for p in self._plugins.values() if p.enabled]),
}
},
"enabled_components": len([c for c in self._components.values() if c.enabled]),
"enabled_plugins": len([p for p in self._plugins.values() if p.enabled]),
}
# === 组件移除相关 ===

View File

@@ -447,13 +447,14 @@ class PluginManager:
tool_count = stats.get("tool_components", 0)
event_handler_count = stats.get("event_handlers", 0)
plus_command_count = stats.get("plus_command_components", 0)
chatter_count = stats.get("chatter_components", 0)
total_components = stats.get("total_components", 0)
# 📋 显示插件加载总览
if total_registered > 0:
logger.info("🎉 插件系统加载完成!")
logger.info(
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count})"
f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count}, Tool: {tool_count}, PlusCommand: {plus_command_count}, EventHandler: {event_handler_count}, Chatter: {chatter_count})"
)
# 显示详细的插件列表
@@ -509,6 +510,12 @@ class PluginManager:
if plus_command_components:
plus_command_names = [c.name for c in plus_command_components]
logger.info(f" ⚡ PlusCommand组件: {', '.join(plus_command_names)}")
chatter_components = [
c for c in plugin_info.components if c.component_type == ComponentType.CHATTER
]
if chatter_components:
chatter_names = [c.name for c in chatter_components]
logger.info(f" 🗣️ Chatter组件: {', '.join(chatter_names)}")
if event_handler_components:
event_handler_names = [c.name for c in event_handler_components]
logger.info(f" 📢 EventHandler组件: {', '.join(event_handler_names)}")

View File

@@ -0,0 +1,125 @@
# 亲和力聊天处理器插件
## 概述
这是一个内置的chatter插件实现了基于亲和力流的智能聊天处理器具有兴趣度评分和人物关系构建功能。
## 功能特性
- **智能兴趣度评分**: 自动识别和评估用户兴趣话题
- **人物关系系统**: 根据互动历史建立和维持用户关系
- **多聊天类型支持**: 支持私聊和群聊场景
- **插件化架构**: 完全集成到插件系统中
## 组件架构
### BaseChatter (抽象基类)
- 位置: `src/plugin_system/base/base_chatter.py`
- 功能: 定义所有chatter组件的基础接口
- 必须实现的方法: `execute(context: StreamContext) -> dict`
### ChatterManager (管理器)
- 位置: `src/chat/chatter_manager.py`
- 功能: 管理和调度所有chatter组件
- 特性: 自动从插件系统注册和发现chatter组件
### AffinityChatter (具体实现)
- 位置: `src/plugins/built_in/chatter/affinity_chatter.py`
- 功能: 亲和力流聊天处理器的具体实现
- 支持的聊天类型: PRIVATE, GROUP
## 使用方法
### 1. 基本使用
```python
from src.chat.chatter_manager import ChatterManager
from src.chat.planner_actions.action_manager import ActionManager
# 初始化
action_manager = ActionManager()
chatter_manager = ChatterManager(action_manager)
# 处理消息流
result = await chatter_manager.process_stream_context(stream_id, context)
```
### 2. 创建自定义Chatter
```python
from src.plugin_system.base.base_chatter import BaseChatter
from src.plugin_system.base.component_types import ChatType, ComponentType
from src.plugin_system.base.component_types import ChatterInfo
class CustomChatter(BaseChatter):
chat_types = [ChatType.PRIVATE] # 只支持私聊
async def execute(self, context: StreamContext) -> dict:
# 实现你的聊天逻辑
return {"success": True, "message": "处理完成"}
# 在插件中注册
async def on_load(self):
chatter_info = ChatterInfo(
name="custom_chatter",
component_type=ComponentType.CHATTER,
description="自定义聊天处理器",
enabled=True,
plugin_name=self.name,
chat_type_allow=ChatType.PRIVATE
)
ComponentRegistry.register_component(
component_info=chatter_info,
component_class=CustomChatter
)
```
## 配置
### 插件配置文件
- 位置: `src/plugins/built_in/chatter/_manifest.json`
- 包含插件信息和组件配置
### 聊天类型
- `PRIVATE`: 私聊
- `GROUP`: 群聊
- `ALL`: 所有类型
## 核心概念
### 1. 兴趣值系统
- 自动识别同类话题
- 兴趣值会根据聊天频率增减
- 支持新话题的自动学习
### 2. 人物关系系统
- 根据互动质量建立关系分
- 不同关系分对应不同的回复风格
- 支持情感化的交流
### 3. 执行流程
1. 接收StreamContext
2. 使用ActionPlanner进行规划
3. 执行相应的Action
4. 返回处理结果
## 扩展开发
### 添加新的Chatter类型
1. 继承BaseChatter类
2. 实现execute方法
3. 在插件中注册组件
4. 配置支持的聊天类型
### 集成现有功能
- 使用ActionPlanner进行动作规划
- 通过ActionManager执行动作
- 利用现有的记忆和知识系统
## 注意事项
1. 所有chatter组件必须实现`execute`方法
2. 插件注册时需要指定支持的聊天类型
3. 组件名称不能包含点号(.)
4. 确保在插件卸载时正确清理资源

View File

@@ -0,0 +1,8 @@
"""
亲和力聊天处理器插件
"""
from .plugin import AffinityChatterPlugin
from .affinity_chatter import AffinityChatter
__all__ = ["AffinityChatterPlugin", "AffinityChatter"]

View File

@@ -0,0 +1,23 @@
{
"manifest_version": 1,
"name": "affinity_chatter",
"display_name": "Affinity Flow Chatter",
"description": "Built-in chatter plugin for affinity flow with interest scoring and relationship building",
"version": "1.0.0",
"author": "MoFox",
"plugin_class": "AffinityChatterPlugin",
"enabled": true,
"is_built_in": true,
"components": [
{
"name": "affinity_chatter",
"type": "chatter",
"description": "Affinity flow chatter with intelligent interest scoring and relationship building",
"enabled": true,
"chat_type_allow": ["all"]
}
],
"host_application": { "min_version": "0.8.0" },
"keywords": ["chatter", "affinity", "conversation"],
"categories": ["Chat", "AI"]
}

View File

@@ -0,0 +1,206 @@
"""
亲和力聊天处理器
基于现有的AffinityFlowChatter重构为插件化组件
"""
import time
import traceback
from datetime import datetime
from typing import Dict, Any
from src.plugin_system.base.base_chatter import BaseChatter
from src.plugin_system.base.component_types import ChatType, ChatMode
from src.common.data_models.message_manager_data_model import StreamContext
from src.chat.planner_actions.planner import ActionPlanner
from src.chat.planner_actions.action_manager import ActionManager
from src.common.logger import get_logger
logger = get_logger("affinity_chatter")
class AffinityChatter(BaseChatter):
"""亲和力聊天处理器"""
chatter_name: str = "AffinityChatter"
chatter_description: str = "基于亲和力模型的智能聊天处理器,支持多种聊天类型"
chat_types: list[ChatType] = [ChatType.ALL] # 支持所有聊天类型
def __init__(self, stream_id: str, planner: ActionPlanner, action_manager: ActionManager):
"""
初始化亲和力聊天处理器
Args:
stream_id: 聊天流ID
planner: 动作规划器
action_manager: 动作管理器
"""
super().__init__(stream_id, planner, action_manager)
# 处理器统计
self.stats = {
"messages_processed": 0,
"plans_created": 0,
"actions_executed": 0,
"successful_executions": 0,
"failed_executions": 0,
}
self.last_activity_time = time.time()
async def execute(self, context: StreamContext) -> dict:
"""
处理StreamContext对象
Args:
context: StreamContext对象包含聊天流的所有消息信息
Returns:
处理结果字典
"""
try:
unread_messages = context.get_unread_messages()
# 使用增强版规划器处理消息
actions, target_message = await self.planner.plan(mode=ChatMode.FOCUS, context=context)
self.stats["plans_created"] += 1
# 执行动作(如果规划器返回了动作)
execution_result = {"executed_count": len(actions) if actions else 0}
if actions:
logger.debug(f"聊天流 {self.stream_id} 生成了 {len(actions)} 个动作")
# 更新统计
self.stats["messages_processed"] += 1
self.stats["actions_executed"] += execution_result.get("executed_count", 0)
self.stats["successful_executions"] += 1
self.last_activity_time = time.time()
result = {
"success": True,
"stream_id": self.stream_id,
"plan_created": True,
"actions_count": len(actions) if actions else 0,
"has_target_message": target_message is not None,
"unread_messages_processed": len(unread_messages),
**execution_result,
}
logger.info(
f"聊天流 {self.stream_id} StreamContext处理成功: 动作数={result['actions_count']}, 未读消息={result['unread_messages_processed']}"
)
return result
except Exception as e:
logger.error(f"亲和力聊天处理器 {self.stream_id} 处理StreamContext时出错: {e}\n{traceback.format_exc()}")
self.stats["failed_executions"] += 1
self.last_activity_time = time.time()
return {
"success": False,
"stream_id": self.stream_id,
"error_message": str(e),
"executed_count": 0,
}
def get_stats(self) -> Dict[str, Any]:
"""
获取处理器统计信息
Returns:
统计信息字典
"""
return self.stats.copy()
def get_planner_stats(self) -> Dict[str, Any]:
"""
获取规划器统计信息
Returns:
规划器统计信息字典
"""
return self.planner.get_planner_stats()
def get_interest_scoring_stats(self) -> Dict[str, Any]:
"""
获取兴趣度评分统计信息
Returns:
兴趣度评分统计信息字典
"""
return self.planner.get_interest_scoring_stats()
def get_relationship_stats(self) -> Dict[str, Any]:
"""
获取用户关系统计信息
Returns:
用户关系统计信息字典
"""
return self.planner.get_relationship_stats()
def get_user_relationship(self, user_id: str) -> float:
"""
获取用户关系分
Args:
user_id: 用户ID
Returns:
用户关系分 (0.0-1.0)
"""
return self.planner.get_user_relationship(user_id)
def update_interest_keywords(self, new_keywords: dict):
"""
更新兴趣关键词
Args:
new_keywords: 新的兴趣关键词字典
"""
self.planner.update_interest_keywords(new_keywords)
logger.info(f"聊天流 {self.stream_id} 已更新兴趣关键词: {list(new_keywords.keys())}")
def reset_stats(self):
"""重置统计信息"""
self.stats = {
"messages_processed": 0,
"plans_created": 0,
"actions_executed": 0,
"successful_executions": 0,
"failed_executions": 0,
}
def is_active(self, max_inactive_minutes: int = 60) -> bool:
"""
检查处理器是否活跃
Args:
max_inactive_minutes: 最大不活跃分钟数
Returns:
是否活跃
"""
current_time = time.time()
max_inactive_seconds = max_inactive_minutes * 60
return (current_time - self.last_activity_time) < max_inactive_seconds
def get_activity_time(self) -> float:
"""
获取最后活动时间
Returns:
最后活动时间戳
"""
return self.last_activity_time
def __str__(self) -> str:
"""字符串表示"""
return f"AffinityChatter(stream_id={self.stream_id}, messages={self.stats['messages_processed']})"
def __repr__(self) -> str:
"""详细字符串表示"""
return (
f"AffinityChatter(stream_id={self.stream_id}, "
f"messages_processed={self.stats['messages_processed']}, "
f"plans_created={self.stats['plans_created']}, "
f"last_activity={datetime.fromtimestamp(self.last_activity_time)})"
)

View File

@@ -0,0 +1,46 @@
"""
亲和力聊天处理器插件
"""
from typing import List, Tuple, Type
from src.plugin_system.apis.plugin_register_api import register_plugin
from src.plugin_system.base.base_plugin import BasePlugin
from src.plugin_system.base.component_types import ComponentInfo, ChatterInfo, ComponentType, ChatType
from src.common.logger import get_logger
logger = get_logger("affinity_chatter_plugin")
@register_plugin
class AffinityChatterPlugin(BasePlugin):
"""亲和力聊天处理器插件
- 延迟导入 `AffinityChatter` 并通过组件注册器注册为聊天处理器
- 提供 `get_plugin_components` 以兼容插件注册机制
"""
plugin_name: str = "affinity_chatter"
enable_plugin: bool = True
dependencies: list[str] = []
python_dependencies: list[str] = []
config_file_name: str = ""
# 简单的 config_schema 占位(如果将来需要配置可扩展)
config_schema = {}
def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
"""返回插件包含的组件列表ChatterInfo, AffinityChatter
这里采用延迟导入 AffinityChatter 来避免循环依赖和启动顺序问题。
如果导入失败则返回空列表以让注册过程继续而不崩溃。
"""
try:
# 延迟导入以避免循环导入
from .affinity_chatter import AffinityChatter
return [(AffinityChatter.get_chatter_info(), AffinityChatter)]
except Exception as e:
logger.error(f"加载 AffinityChatter 时出错: {e}")
return []