feat: 添加KFC V2专属动作模块及相关功能,优化回复机制
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
AFC 专属动作模块
|
||||
"""
|
||||
|
||||
from .reply import ReplyAction, RespondAction
|
||||
|
||||
__all__ = ["ReplyAction", "RespondAction"]
|
||||
@@ -1,21 +1,23 @@
|
||||
"""
|
||||
回复动作模块
|
||||
AFC 回复动作模块
|
||||
|
||||
定义了三种回复相关动作:
|
||||
定义了两种回复相关动作:
|
||||
- reply: 针对单条消息的深度回复(使用 s4u 模板)
|
||||
- respond: 对未读消息的统一回应(使用 normal 模板)
|
||||
- no_reply: 选择不回复
|
||||
|
||||
这些动作是 AffinityFlowChatter 的专属动作。
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import ClassVar
|
||||
|
||||
from src.common.data_models.database_data_model import DatabaseMessages
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from src.plugin_system import ActionActivationType, BaseAction, ChatMode
|
||||
from src.plugin_system.apis import database_api, generator_api, send_api
|
||||
from src.plugin_system.apis import generator_api, send_api
|
||||
|
||||
logger = get_logger("reply_actions")
|
||||
logger = get_logger("afc_reply_actions")
|
||||
|
||||
|
||||
class ReplyAction(BaseAction):
|
||||
@@ -63,8 +65,11 @@ class ReplyAction(BaseAction):
|
||||
async def execute(self) -> tuple[bool, str]:
|
||||
"""执行reply动作 - 完整的回复流程"""
|
||||
try:
|
||||
# 确保 action_message 是 DatabaseMessages 类型,否则使用 None
|
||||
reply_message = self.action_message if isinstance(self.action_message, DatabaseMessages) else None
|
||||
|
||||
# 检查目标消息是否为表情包
|
||||
if self.action_message and getattr(self.action_message, "is_emoji", False):
|
||||
if reply_message and getattr(reply_message, "is_emoji", False):
|
||||
if not getattr(global_config.chat, "allow_reply_to_emoji", True):
|
||||
logger.info(f"{self.log_prefix} 目标消息为表情包且配置不允许回复,跳过")
|
||||
return True, ""
|
||||
@@ -76,9 +81,9 @@ class ReplyAction(BaseAction):
|
||||
# 生成回复
|
||||
success, response_set, _ = await generator_api.generate_reply(
|
||||
chat_stream=self.chat_stream,
|
||||
reply_message=self.action_message,
|
||||
reply_message=reply_message,
|
||||
action_data=action_data,
|
||||
available_actions={self.action_name: None},
|
||||
available_actions={self.action_name: self.get_action_info()},
|
||||
enable_tool=global_config.tool.enable_tool,
|
||||
request_type="chat.replyer",
|
||||
from_plugin=False,
|
||||
@@ -91,9 +96,6 @@ class ReplyAction(BaseAction):
|
||||
# 发送回复
|
||||
reply_text = await self._send_response(response_set)
|
||||
|
||||
# 存储动作信息
|
||||
await self._store_action_info(reply_text)
|
||||
|
||||
logger.info(f"{self.log_prefix} reply 动作执行成功")
|
||||
return True, reply_text
|
||||
|
||||
@@ -112,6 +114,9 @@ class ReplyAction(BaseAction):
|
||||
should_quote = self.action_data.get("should_quote_reply", False)
|
||||
first_sent = False
|
||||
|
||||
# 确保 action_message 是 DatabaseMessages 类型
|
||||
reply_message = self.action_message if isinstance(self.action_message, DatabaseMessages) else None
|
||||
|
||||
for reply_seg in response_set:
|
||||
# 处理元组格式
|
||||
if isinstance(reply_seg, tuple) and len(reply_seg) >= 2:
|
||||
@@ -129,8 +134,8 @@ class ReplyAction(BaseAction):
|
||||
await send_api.text_to_stream(
|
||||
text=data,
|
||||
stream_id=self.chat_stream.stream_id,
|
||||
reply_to_message=self.action_message,
|
||||
set_reply=should_quote and bool(self.action_message),
|
||||
reply_to_message=reply_message,
|
||||
set_reply=should_quote and bool(reply_message),
|
||||
typing=False,
|
||||
)
|
||||
first_sent = True
|
||||
@@ -144,33 +149,6 @@ class ReplyAction(BaseAction):
|
||||
)
|
||||
|
||||
return reply_text
|
||||
|
||||
async def _store_action_info(self, reply_text: str):
|
||||
"""存储动作信息到数据库"""
|
||||
from src.person_info.person_info import get_person_info_manager
|
||||
|
||||
person_info_manager = get_person_info_manager()
|
||||
|
||||
if self.action_message:
|
||||
platform = self.action_message.chat_info.platform
|
||||
user_id = self.action_message.user_info.user_id
|
||||
else:
|
||||
platform = getattr(self.chat_stream, "platform", "unknown")
|
||||
user_id = ""
|
||||
|
||||
person_id = person_info_manager.get_person_id(platform, user_id)
|
||||
person_name = await person_info_manager.get_value(person_id, "person_name")
|
||||
action_prompt_display = f"你对{person_name}进行了回复:{reply_text}"
|
||||
|
||||
await database_api.store_action_info(
|
||||
chat_stream=self.chat_stream,
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=action_prompt_display,
|
||||
action_done=True,
|
||||
thinking_id=self.thinking_id,
|
||||
action_data={"reply_text": reply_text},
|
||||
action_name="reply",
|
||||
)
|
||||
|
||||
|
||||
class RespondAction(BaseAction):
|
||||
@@ -220,12 +198,15 @@ class RespondAction(BaseAction):
|
||||
action_data = self.action_data.copy()
|
||||
action_data["prompt_mode"] = "normal"
|
||||
|
||||
# 确保 action_message 是 DatabaseMessages 类型,否则使用 None
|
||||
reply_message = self.action_message if isinstance(self.action_message, DatabaseMessages) else None
|
||||
|
||||
# 生成回复
|
||||
success, response_set, _ = await generator_api.generate_reply(
|
||||
chat_stream=self.chat_stream,
|
||||
reply_message=self.action_message,
|
||||
reply_message=reply_message,
|
||||
action_data=action_data,
|
||||
available_actions={self.action_name: None},
|
||||
available_actions={self.action_name: self.get_action_info()},
|
||||
enable_tool=global_config.tool.enable_tool,
|
||||
request_type="chat.replyer",
|
||||
from_plugin=False,
|
||||
@@ -238,9 +219,6 @@ class RespondAction(BaseAction):
|
||||
# 发送回复(respond 默认不引用)
|
||||
reply_text = await self._send_response(response_set)
|
||||
|
||||
# 存储动作信息
|
||||
await self._store_action_info(reply_text)
|
||||
|
||||
logger.info(f"{self.log_prefix} respond 动作执行成功")
|
||||
return True, reply_text
|
||||
|
||||
@@ -288,15 +266,3 @@ class RespondAction(BaseAction):
|
||||
)
|
||||
|
||||
return reply_text
|
||||
|
||||
async def _store_action_info(self, reply_text: str):
|
||||
"""存储动作信息到数据库"""
|
||||
await database_api.store_action_info(
|
||||
chat_stream=self.chat_stream,
|
||||
action_build_into_prompt=False,
|
||||
action_prompt_display=f"统一回应:{reply_text}",
|
||||
action_done=True,
|
||||
thinking_id=self.thinking_id,
|
||||
action_data={"reply_text": reply_text},
|
||||
action_name="respond",
|
||||
)
|
||||
@@ -66,13 +66,6 @@ class ChatterPlanExecutor:
|
||||
action_types = [action.action_type for action in plan.decided_actions]
|
||||
logger.info(f"选择动作: {', '.join(action_types) if action_types else '无'}")
|
||||
|
||||
# 根据配置决定是否启用批量存储模式
|
||||
if global_config.database.batch_action_storage_enabled:
|
||||
self.action_manager.enable_batch_storage(plan.chat_id)
|
||||
logger.debug("已启用批量存储模式")
|
||||
else:
|
||||
logger.debug("批量存储功能已禁用,使用立即存储模式")
|
||||
|
||||
execution_results = []
|
||||
reply_actions = []
|
||||
other_actions = []
|
||||
@@ -109,9 +102,6 @@ class ChatterPlanExecutor:
|
||||
f"规划执行完成: 总数={len(plan.decided_actions)}, 成功={successful_count}, 失败={len(execution_results) - successful_count}"
|
||||
)
|
||||
|
||||
# 批量存储所有待处理的动作
|
||||
await self._flush_action_manager_batch_storage(plan)
|
||||
|
||||
return {
|
||||
"executed_count": len(plan.decided_actions),
|
||||
"successful_count": successful_count,
|
||||
@@ -530,25 +520,3 @@ class ChatterPlanExecutor:
|
||||
}
|
||||
for i, time_val in enumerate(recent_times)
|
||||
]
|
||||
|
||||
async def _flush_action_manager_batch_storage(self, plan: Plan):
|
||||
"""使用 action_manager 的批量存储功能存储所有待处理的动作"""
|
||||
try:
|
||||
# 通过 chat_id 获取真实的 chat_stream 对象
|
||||
from src.plugin_system.apis.chat_api import get_chat_manager
|
||||
|
||||
chat_manager = get_chat_manager()
|
||||
chat_stream = await chat_manager.get_stream(plan.chat_id)
|
||||
|
||||
if chat_stream:
|
||||
# 调用 action_manager 的批量存储
|
||||
await self.action_manager.flush_batch_storage(chat_stream)
|
||||
logger.info("批量存储完成:通过 action_manager 存储所有动作记录")
|
||||
|
||||
# 禁用批量存储模式
|
||||
self.action_manager.disable_batch_storage()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量存储动作记录时发生错误: {e}")
|
||||
# 确保在出错时也禁用批量存储模式
|
||||
self.action_manager.disable_batch_storage()
|
||||
|
||||
@@ -86,4 +86,20 @@ class AffinityChatterPlugin(BasePlugin):
|
||||
except Exception as e:
|
||||
logger.error(f"加载 ProactiveThinkingMessageHandler 时出错: {e}")
|
||||
|
||||
try:
|
||||
# 延迟导入 ReplyAction(AFC 专属动作)
|
||||
from .actions.reply import ReplyAction
|
||||
|
||||
components.append((ReplyAction.get_action_info(), ReplyAction))
|
||||
except Exception as e:
|
||||
logger.error(f"加载 ReplyAction 时出错: {e}")
|
||||
|
||||
try:
|
||||
# 延迟导入 RespondAction(AFC 专属动作)
|
||||
from .actions.reply import RespondAction
|
||||
|
||||
components.append((RespondAction.get_action_info(), RespondAction))
|
||||
except Exception as e:
|
||||
logger.error(f"加载 RespondAction 时出错: {e}")
|
||||
|
||||
return components
|
||||
|
||||
@@ -219,8 +219,7 @@ class EmojiAction(BaseAction):
|
||||
)
|
||||
emoji_base64, emoji_description = random.choice(all_emojis_data)
|
||||
|
||||
assert global_config is not None
|
||||
if global_config.emoji.emoji_selection_mode == "description":
|
||||
elif global_config.emoji.emoji_selection_mode == "description":
|
||||
# --- 详细描述选择模式 ---
|
||||
# 获取最近的5条消息内容用于判断
|
||||
recent_messages = await message_api.get_recent_messages(chat_id=self.chat_id, limit=20)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""
|
||||
核心动作插件
|
||||
|
||||
将系统核心动作(reply、no_reply、emoji)转换为新插件系统格式
|
||||
将系统核心动作(emoji)转换为新插件系统格式
|
||||
这是系统的内置插件,提供基础的聊天交互功能
|
||||
|
||||
注意:reply 和 respond 动作已移至 AffinityFlowChatter 插件
|
||||
"""
|
||||
|
||||
# 导入依赖的系统组件
|
||||
@@ -16,7 +18,6 @@ from src.plugin_system.base.config_types import ConfigField
|
||||
|
||||
# 导入API模块 - 标准Python包方式
|
||||
from src.plugins.built_in.core_actions.emoji import EmojiAction
|
||||
from src.plugins.built_in.core_actions.reply import ReplyAction, RespondAction
|
||||
|
||||
logger = get_logger("core_actions")
|
||||
|
||||
@@ -26,11 +27,11 @@ class CoreActionsPlugin(BasePlugin):
|
||||
"""核心动作插件
|
||||
|
||||
系统内置插件,提供基础的聊天交互功能:
|
||||
- Reply: 回复动作
|
||||
- NoReply: 不回复动作
|
||||
- Emoji: 表情动作
|
||||
|
||||
注意:插件基本信息优先从_manifest.json文件中读取
|
||||
注意:
|
||||
- reply 和 respond 动作已移至 AffinityFlowChatter 插件
|
||||
- 插件基本信息优先从_manifest.json文件中读取
|
||||
"""
|
||||
|
||||
# 插件基本信息
|
||||
@@ -53,8 +54,6 @@ class CoreActionsPlugin(BasePlugin):
|
||||
"config_version": ConfigField(type=str, default="0.6.0", description="配置文件版本"),
|
||||
},
|
||||
"components": {
|
||||
"enable_reply": ConfigField(type=bool, default=True, description="是否启用 reply 动作(s4u模板)"),
|
||||
"enable_respond": ConfigField(type=bool, default=True, description="是否启用 respond 动作(normal模板)"),
|
||||
"enable_emoji": ConfigField(type=bool, default=True, description="是否启用发送表情/图片动作"),
|
||||
},
|
||||
}
|
||||
@@ -65,14 +64,6 @@ class CoreActionsPlugin(BasePlugin):
|
||||
# --- 根据配置注册组件 ---
|
||||
components: ClassVar = []
|
||||
|
||||
# 注册 reply 动作
|
||||
if self.get_config("components.enable_reply", True):
|
||||
components.append((ReplyAction.get_action_info(), ReplyAction))
|
||||
|
||||
# 注册 respond 动作
|
||||
if self.get_config("components.enable_respond", True):
|
||||
components.append((RespondAction.get_action_info(), RespondAction))
|
||||
|
||||
# 注册 emoji 动作
|
||||
if self.get_config("components.enable_emoji", True):
|
||||
components.append((EmojiAction.get_action_info(), EmojiAction))
|
||||
|
||||
@@ -33,6 +33,18 @@ from .config import (
|
||||
reload_config,
|
||||
)
|
||||
from .plugin import KokoroFlowChatterV2Plugin
|
||||
from src.plugin_system.base.plugin_metadata import PluginMetadata
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Kokoro Flow Chatter",
|
||||
description="专为私聊设计的深度情感交互处理器,实现心理状态驱动的对话体验",
|
||||
usage="在私聊场景中自动启用,可通过 [kokoro_flow_chatter].enable 配置开关",
|
||||
version="2.0.0",
|
||||
author="MoFox",
|
||||
keywords=["chatter", "kokoro", "private", "emotional", "narrative"],
|
||||
categories=["Chat", "AI", "Emotional"],
|
||||
extra={"is_built_in": True, "chat_type": "private"},
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Models
|
||||
@@ -61,4 +73,5 @@ __all__ = [
|
||||
"reload_config",
|
||||
# Plugin
|
||||
"KokoroFlowChatterV2Plugin",
|
||||
"__plugin_meta__",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
KFC V2 专属动作模块
|
||||
"""
|
||||
|
||||
from .reply import KFCReplyAction
|
||||
|
||||
__all__ = ["KFCReplyAction"]
|
||||
82
src/plugins/built_in/kokoro_flow_chatter_v2/actions/reply.py
Normal file
82
src/plugins/built_in/kokoro_flow_chatter_v2/actions/reply.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
KFC V2 回复动作模块
|
||||
|
||||
KFC 的 reply 动作与 AFC 不同:
|
||||
- 不调用 LLM 生成回复,content 由 Replyer 提前生成
|
||||
- 动作本身只负责发送 content 参数中的内容
|
||||
"""
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system import ActionActivationType, BaseAction, ChatMode
|
||||
from src.plugin_system.apis import send_api
|
||||
|
||||
logger = get_logger("kfc_reply_action")
|
||||
|
||||
|
||||
class KFCReplyAction(BaseAction):
|
||||
"""KFC Reply 动作 - 发送已生成的回复内容
|
||||
|
||||
特点:
|
||||
- 不调用 LLM,直接发送 content 参数中的内容
|
||||
- content 由 Replyer 提前生成
|
||||
- 仅限 KokoroFlowChatterV2 使用
|
||||
"""
|
||||
|
||||
# 动作基本信息
|
||||
action_name = "reply"
|
||||
action_description = "发送回复消息。content 参数包含要发送的内容。"
|
||||
|
||||
# 激活设置
|
||||
activation_type = ActionActivationType.ALWAYS
|
||||
mode_enable = ChatMode.ALL
|
||||
parallel_action = False
|
||||
|
||||
# Chatter 限制:仅允许 KokoroFlowChatterV2 使用
|
||||
chatter_allow: ClassVar[list[str]] = ["KokoroFlowChatterV2"]
|
||||
|
||||
# 动作参数定义
|
||||
action_parameters: ClassVar = {
|
||||
"content": "要发送的回复内容(必需,由 Replyer 生成)",
|
||||
"should_quote_reply": "是否引用原消息(可选,true/false,默认 false)",
|
||||
}
|
||||
|
||||
# 动作使用场景
|
||||
action_require: ClassVar = [
|
||||
"发送回复消息时使用",
|
||||
"content 参数必须包含要发送的内容",
|
||||
]
|
||||
|
||||
# 关联类型
|
||||
associated_types: ClassVar[list[str]] = ["text"]
|
||||
|
||||
async def execute(self) -> tuple[bool, str]:
|
||||
"""执行 reply 动作 - 发送 content 中的内容"""
|
||||
try:
|
||||
# 获取要发送的内容
|
||||
content = self.action_data.get("content", "")
|
||||
if not content:
|
||||
logger.warning(f"{self.log_prefix} content 为空,跳过发送")
|
||||
return True, ""
|
||||
|
||||
# 获取是否引用
|
||||
should_quote = self.action_data.get("should_quote_reply", False)
|
||||
|
||||
# 发送消息
|
||||
await send_api.text_to_stream(
|
||||
text=content,
|
||||
stream_id=self.chat_stream.stream_id,
|
||||
reply_to_message=self.action_message,
|
||||
set_reply=should_quote and bool(self.action_message),
|
||||
typing=False,
|
||||
)
|
||||
|
||||
logger.info(f"{self.log_prefix} KFC reply 动作执行成功")
|
||||
return True, content
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.log_prefix} KFC reply 动作执行失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False, ""
|
||||
@@ -9,7 +9,7 @@ from typing import Any, ClassVar
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.base.base_plugin import BasePlugin
|
||||
from src.plugin_system.base.component_types import ChatterInfo
|
||||
from src.plugin_system.decorators import register_plugin
|
||||
from src.plugin_system import register_plugin
|
||||
|
||||
from .chatter import KokoroFlowChatterV2
|
||||
from .config import get_config
|
||||
@@ -84,7 +84,19 @@ class KokoroFlowChatterV2Plugin(BasePlugin):
|
||||
))
|
||||
logger.debug("[KFC V2] 成功加载 KokoroFlowChatterV2 组件")
|
||||
except Exception as e:
|
||||
logger.error(f"[KFC V2] 加载组件失败: {e}")
|
||||
logger.error(f"[KFC V2] 加载 Chatter 组件失败: {e}")
|
||||
|
||||
try:
|
||||
# 注册 KFC 专属 Reply 动作
|
||||
from .actions.reply import KFCReplyAction
|
||||
|
||||
components.append((
|
||||
KFCReplyAction.get_action_info(),
|
||||
KFCReplyAction,
|
||||
))
|
||||
logger.debug("[KFC V2] 成功加载 KFCReplyAction 组件")
|
||||
except Exception as e:
|
||||
logger.error(f"[KFC V2] 加载 Reply 动作失败: {e}")
|
||||
|
||||
return components
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class NapcatAdapter(BaseAdapter):
|
||||
adapter_description = "基于 MoFox-Bus 的 Napcat/OneBot 11 适配器"
|
||||
platform = "qq"
|
||||
|
||||
run_in_subprocess = True
|
||||
run_in_subprocess = False
|
||||
|
||||
def __init__(self, core_sink: CoreSink, plugin: Optional[BasePlugin] = None, **kwargs):
|
||||
"""初始化 Napcat 适配器"""
|
||||
|
||||
Reference in New Issue
Block a user