feat: 重命名Kokoro Flow Chatter V2为Kokoro Flow Chatter,更新相关模块和配置

This commit is contained in:
Windpicker-owo
2025-11-30 16:16:22 +08:00
parent c6f34992d1
commit 252e8620e1
10 changed files with 157 additions and 92 deletions

View File

@@ -1,5 +1,5 @@
"""
Kokoro Flow Chatter V2 - 私聊特化的心流聊天器
Kokoro Flow Chatter - 私聊特化的心流聊天器
重构版本,核心设计理念:
1. Chatter 职责极简化:只负责"收到消息 → 规划执行"
@@ -18,7 +18,7 @@ from .models import (
LLMResponse,
)
from .session import KokoroSession, SessionManager, get_session_manager
from .chatter import KokoroFlowChatterV2
from .chatter import KokoroFlowChatter
from .replyer import generate_response
from .proactive_thinker import (
ProactiveThinker,
@@ -27,12 +27,12 @@ from .proactive_thinker import (
stop_proactive_thinker,
)
from .config import (
KokoroFlowChatterV2Config,
KokoroFlowChatterConfig,
get_config,
load_config,
reload_config,
)
from .plugin import KokoroFlowChatterV2Plugin
from .plugin import KokoroFlowChatterPlugin
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
@@ -59,7 +59,7 @@ __all__ = [
"SessionManager",
"get_session_manager",
# Core Components
"KokoroFlowChatterV2",
"KokoroFlowChatter",
"generate_response",
# Proactive Thinker
"ProactiveThinker",
@@ -67,11 +67,11 @@ __all__ = [
"start_proactive_thinker",
"stop_proactive_thinker",
# Config
"KokoroFlowChatterV2Config",
"KokoroFlowChatterConfig",
"get_config",
"load_config",
"reload_config",
# Plugin
"KokoroFlowChatterV2Plugin",
"KokoroFlowChatterPlugin",
"__plugin_meta__",
]

View File

@@ -1,5 +1,5 @@
"""
KFC V2 回复动作模块
KFC 回复动作模块
KFC 的 reply 动作与 AFC 不同:
- 不调用 LLM 生成回复content 由 Replyer 提前生成
@@ -21,7 +21,7 @@ class KFCReplyAction(BaseAction):
特点:
- 不调用 LLM直接发送 content 参数中的内容
- content 由 Replyer 提前生成
- 仅限 KokoroFlowChatterV2 使用
- 仅限 KokoroFlowChatter 使用
注意:使用 kfc_reply 作为动作名称以避免与 AFC 的 reply 动作冲突
"""
@@ -35,8 +35,8 @@ class KFCReplyAction(BaseAction):
mode_enable = ChatMode.ALL
parallel_action = False
# Chatter 限制:仅允许 KokoroFlowChatterV2 使用
chatter_allow: ClassVar[list[str]] = ["KokoroFlowChatterV2"]
# Chatter 限制:仅允许 KokoroFlowChatter 使用
chatter_allow: ClassVar[list[str]] = ["KokoroFlowChatter"]
# 动作参数定义
action_parameters: ClassVar = {

View File

@@ -1,5 +1,5 @@
"""
Kokoro Flow Chatter V2 - Chatter 主类
Kokoro Flow Chatter - Chatter 主类
极简设计,只负责:
1. 收到消息
@@ -25,16 +25,16 @@ from .session import get_session_manager
if TYPE_CHECKING:
pass
logger = get_logger("kfc_v2_chatter")
logger = get_logger("kfc_chatter")
# 控制台颜色
SOFT_PURPLE = "\033[38;5;183m"
RESET = "\033[0m"
class KokoroFlowChatterV2(BaseChatter):
class KokoroFlowChatter(BaseChatter):
"""
Kokoro Flow Chatter V2 - 私聊特化的心流聊天器
Kokoro Flow Chatter - 私聊特化的心流聊天器
核心设计:
- Chatter 只负责 "收到消息 → 规划执行" 的流程
@@ -47,8 +47,8 @@ class KokoroFlowChatterV2(BaseChatter):
- 主动发起对话(由 ProactiveThinker 负责)
"""
chatter_name: str = "KokoroFlowChatterV2"
chatter_description: str = "心流聊天器 V2 - 私聊特化的深度情感交互处理器"
chatter_name: str = "KokoroFlowChatter"
chatter_description: str = "心流聊天器 - 私聊特化的深度情感交互处理器"
chat_types: ClassVar[list[ChatType]] = [ChatType.PRIVATE]
def __init__(
@@ -73,7 +73,7 @@ class KokoroFlowChatterV2(BaseChatter):
"failed_responses": 0,
}
logger.info(f"{SOFT_PURPLE}[KFC V2]{RESET} 初始化完成: stream_id={stream_id}")
logger.info(f"{SOFT_PURPLE}[KFC]{RESET} 初始化完成: stream_id={stream_id}")
async def execute(self, context: StreamContext) -> dict:
"""
@@ -114,7 +114,13 @@ class KokoroFlowChatterV2(BaseChatter):
# 4. 确定 situation_type根据之前的等待状态
situation_type = self._determine_situation_type(session)
# 5. 记录用户消息到 mental_log
# 5. **立即**结束等待状态,防止 ProactiveThinker 并发处理
# 在调用 LLM 之前就结束等待,避免 ProactiveThinker 检测到超时后也开始处理
if session.status == SessionStatus.WAITING:
session.end_waiting()
await self.session_manager.save_session(user_id)
# 6. 记录用户消息到 mental_log
for msg in unread_messages:
msg_content = msg.processed_plain_text or msg.display_message or ""
msg_user_name = msg.user_info.user_nickname if msg.user_info else user_name
@@ -127,17 +133,17 @@ class KokoroFlowChatterV2(BaseChatter):
timestamp=msg.time,
)
# 6. 加载可用动作(通过 ActionModifier 过滤)
# 7. 加载可用动作(通过 ActionModifier 过滤)
from src.chat.planner_actions.action_modifier import ActionModifier
action_modifier = ActionModifier(self.action_manager, self.stream_id)
await action_modifier.modify_actions(chatter_name="KokoroFlowChatterV2")
await action_modifier.modify_actions(chatter_name="KokoroFlowChatter")
available_actions = self.action_manager.get_using_actions()
# 7. 获取聊天流
# 8. 获取聊天流
chat_stream = await self._get_chat_stream()
# 8. 调用 Replyer 生成响应
# 9. 调用 Replyer 生成响应
response = await generate_response(
session=session,
user_name=user_name,
@@ -146,7 +152,7 @@ class KokoroFlowChatterV2(BaseChatter):
available_actions=available_actions,
)
# 9. 执行动作
# 10. 执行动作
exec_results = []
has_reply = False
for action in response.actions:
@@ -157,13 +163,13 @@ class KokoroFlowChatterV2(BaseChatter):
reasoning=response.thought,
action_data=action.params,
thinking_id=None,
log_prefix="[KFC V2]",
log_prefix="[KFC]",
)
exec_results.append(result)
if result.get("success") and action.type in ("kfc_reply", "respond"):
has_reply = True
# 10. 记录 Bot 规划到 mental_log
# 11. 记录 Bot 规划到 mental_log
session.add_bot_planning(
thought=response.thought,
actions=[a.to_dict() for a in response.actions],
@@ -171,7 +177,7 @@ class KokoroFlowChatterV2(BaseChatter):
max_wait_seconds=response.max_wait_seconds,
)
# 11. 更新 Session 状态
# 12. 更新 Session 状态
if response.max_wait_seconds > 0:
session.start_waiting(
expected_reaction=response.expected_reaction,
@@ -180,20 +186,20 @@ class KokoroFlowChatterV2(BaseChatter):
else:
session.end_waiting()
# 12. 标记消息为已读
# 13. 标记消息为已读
for msg in unread_messages:
context.mark_message_as_read(str(msg.message_id))
# 13. 保存 Session
# 14. 保存 Session
await self.session_manager.save_session(user_id)
# 14. 更新统计
# 15. 更新统计
self._stats["messages_processed"] += len(unread_messages)
if has_reply:
self._stats["successful_responses"] += 1
logger.info(
f"{SOFT_PURPLE}[KFC V2]{RESET} 处理完成: "
f"{SOFT_PURPLE}[KFC]{RESET} 处理完成: "
f"user={user_name}, situation={situation_type}, "
f"actions={[a.type for a in response.actions]}, "
f"wait={response.max_wait_seconds}s"
@@ -209,7 +215,7 @@ class KokoroFlowChatterV2(BaseChatter):
except Exception as e:
self._stats["failed_responses"] += 1
logger.error(f"[KFC V2] 处理失败: {e}")
logger.error(f"[KFC] 处理失败: {e}")
import traceback
traceback.print_exc()
return self._build_result(success=False, message=str(e), error=True)
@@ -244,7 +250,7 @@ class KokoroFlowChatterV2(BaseChatter):
if chat_manager:
return await chat_manager.get_stream(self.stream_id)
except Exception as e:
logger.warning(f"[KFC V2] 获取 chat_stream 失败: {e}")
logger.warning(f"[KFC] 获取 chat_stream 失败: {e}")
return None
def _build_result(

View File

@@ -67,7 +67,7 @@ class SessionConfig:
"""会话配置"""
# Session 持久化目录(相对于 data/
session_dir: str = "kokoro_flow_chatter_v2/sessions"
session_dir: str = "kokoro_flow_chatter/sessions"
# Session 自动过期时间(秒),超过此时间未活动自动清理
session_expire_seconds: int = 86400 * 7 # 7 天
@@ -94,8 +94,8 @@ class LLMConfig:
@dataclass
class KokoroFlowChatterV2Config:
"""Kokoro Flow Chatter V2 总配置"""
class KokoroFlowChatterConfig:
"""Kokoro Flow Chatter 总配置"""
# 是否启用
enabled: bool = True
@@ -123,10 +123,10 @@ class KokoroFlowChatterV2Config:
# 全局配置单例
_config: Optional[KokoroFlowChatterV2Config] = None
_config: Optional[KokoroFlowChatterConfig] = None
def get_config() -> KokoroFlowChatterV2Config:
def get_config() -> KokoroFlowChatterConfig:
"""获取全局配置"""
global _config
if _config is None:
@@ -134,19 +134,19 @@ def get_config() -> KokoroFlowChatterV2Config:
return _config
def load_config() -> KokoroFlowChatterV2Config:
"""从全局配置加载 KFC V2 配置"""
def load_config() -> KokoroFlowChatterConfig:
"""从全局配置加载 KFC 配置"""
from src.config.config import global_config
config = KokoroFlowChatterV2Config()
config = KokoroFlowChatterConfig()
# 尝试从全局配置读取
if not global_config:
return config
try:
if hasattr(global_config, 'kokoro_flow_chatter_v2'):
kfc_cfg = getattr(global_config, 'kokoro_flow_chatter_v2')
if hasattr(global_config, 'kokoro_flow_chatter'):
kfc_cfg = getattr(global_config, 'kokoro_flow_chatter')
# 基础配置
if hasattr(kfc_cfg, 'enabled'):
@@ -191,7 +191,7 @@ def load_config() -> KokoroFlowChatterV2Config:
if hasattr(kfc_cfg, 'session'):
sess_cfg = kfc_cfg.session
config.session = SessionConfig(
session_dir=getattr(sess_cfg, 'session_dir', "kokoro_flow_chatter_v2/sessions"),
session_dir=getattr(sess_cfg, 'session_dir', "kokoro_flow_chatter/sessions"),
session_expire_seconds=getattr(sess_cfg, 'session_expire_seconds', 86400 * 7),
max_mental_log_entries=getattr(sess_cfg, 'max_mental_log_entries', 100),
)
@@ -208,13 +208,13 @@ def load_config() -> KokoroFlowChatterV2Config:
except Exception as e:
from src.common.logger import get_logger
logger = get_logger("kfc_v2_config")
logger.warning(f"加载 KFC V2 配置失败,使用默认值: {e}")
logger = get_logger("kfc_config")
logger.warning(f"加载 KFC 配置失败,使用默认值: {e}")
return config
def reload_config() -> KokoroFlowChatterV2Config:
def reload_config() -> KokoroFlowChatterConfig:
"""重新加载配置"""
global _config
_config = load_config()

View File

@@ -1,5 +1,5 @@
"""
Kokoro Flow Chatter V2 - 插件注册
Kokoro Flow Chatter - 插件注册
注册 Chatter
"""
@@ -11,17 +11,17 @@ from src.plugin_system.base.base_plugin import BasePlugin
from src.plugin_system.base.component_types import ChatterInfo
from src.plugin_system import register_plugin
from .chatter import KokoroFlowChatterV2
from .chatter import KokoroFlowChatter
from .config import get_config
from .proactive_thinker import start_proactive_thinker, stop_proactive_thinker
logger = get_logger("kfc_v2_plugin")
logger = get_logger("kfc_plugin")
@register_plugin
class KokoroFlowChatterV2Plugin(BasePlugin):
class KokoroFlowChatterPlugin(BasePlugin):
"""
Kokoro Flow Chatter V2 插件
Kokoro Flow Chatter 插件
专为私聊设计的增强 Chatter
- 线性叙事提示词架构
@@ -29,7 +29,7 @@ class KokoroFlowChatterV2Plugin(BasePlugin):
- 主动思考能力
"""
plugin_name: str = "kokoro_flow_chatter_v2"
plugin_name: str = "kokoro_flow_chatter"
enable_plugin: bool = True
plugin_priority: int = 50 # 高于默认 Chatter
dependencies: ClassVar[list[str]] = []
@@ -44,28 +44,28 @@ class KokoroFlowChatterV2Plugin(BasePlugin):
config = get_config()
if not config.enabled:
logger.info("[KFC V2] 插件已禁用")
logger.info("[KFC] 插件已禁用")
return
logger.info("[KFC V2] 插件已加载")
logger.info("[KFC] 插件已加载")
# 启动主动思考器
if config.proactive.enabled:
try:
await start_proactive_thinker()
logger.info("[KFC V2] 主动思考器已启动")
logger.info("[KFC] 主动思考器已启动")
self._is_started = True
except Exception as e:
logger.error(f"[KFC V2] 启动主动思考器失败: {e}")
logger.error(f"[KFC] 启动主动思考器失败: {e}")
async def on_plugin_unloaded(self):
"""插件卸载时"""
try:
await stop_proactive_thinker()
logger.info("[KFC V2] 主动思考器已停止")
logger.info("[KFC] 主动思考器已停止")
self._is_started = False
except Exception as e:
logger.warning(f"[KFC V2] 停止主动思考器失败: {e}")
logger.warning(f"[KFC] 停止主动思考器失败: {e}")
def get_plugin_components(self):
"""返回组件列表"""
@@ -79,12 +79,12 @@ class KokoroFlowChatterV2Plugin(BasePlugin):
try:
# 注册 Chatter
components.append((
KokoroFlowChatterV2.get_chatter_info(),
KokoroFlowChatterV2,
KokoroFlowChatter.get_chatter_info(),
KokoroFlowChatter,
))
logger.debug("[KFC V2] 成功加载 KokoroFlowChatterV2 组件")
logger.debug("[KFC] 成功加载 KokoroFlowChatter 组件")
except Exception as e:
logger.error(f"[KFC V2] 加载 Chatter 组件失败: {e}")
logger.error(f"[KFC] 加载 Chatter 组件失败: {e}")
try:
# 注册 KFC 专属 Reply 动作
@@ -94,9 +94,9 @@ class KokoroFlowChatterV2Plugin(BasePlugin):
KFCReplyAction.get_action_info(),
KFCReplyAction,
))
logger.debug("[KFC V2] 成功加载 KFCReplyAction 组件")
logger.debug("[KFC] 成功加载 KFCReplyAction 组件")
except Exception as e:
logger.error(f"[KFC V2] 加载 Reply 动作失败: {e}")
logger.error(f"[KFC] 加载 Reply 动作失败: {e}")
return components
@@ -104,7 +104,7 @@ class KokoroFlowChatterV2Plugin(BasePlugin):
"""获取插件信息"""
return {
"name": self.plugin_name,
"display_name": "Kokoro Flow Chatter V2",
"display_name": "Kokoro Flow Chatter",
"version": "2.0.0",
"author": "MoFox",
"description": "专为私聊设计的增强 Chatter",

View File

@@ -80,13 +80,16 @@ class PromptBuilder:
session, user_name, situation_type, extra_context
)
# 5. 构建可用动作
# 5. 构建聊天历史总览
chat_history_block = await self._build_chat_history_block(chat_stream)
# 6. 构建可用动作
actions_block = self._build_actions_block(available_actions)
# 6. 获取输出格式
# 7. 获取输出格式
output_format = await self._get_output_format()
# 7. 使用统一的 prompt 管理系统格式化
# 8. 使用统一的 prompt 管理系统格式化
prompt = await global_prompt_manager.format_prompt(
PROMPT_NAMES["main"],
user_name=user_name,
@@ -94,6 +97,7 @@ class PromptBuilder:
relation_block=relation_block,
activity_stream=activity_stream or "(这是你们第一次聊天)",
current_situation=current_situation,
chat_history_block=chat_history_block,
available_actions=actions_block,
output_format=output_format,
)
@@ -155,6 +159,57 @@ class PromptBuilder:
return f"你与 {user_name} 还不太熟悉,这是早期的交流阶段。"
async def _build_chat_history_block(
self,
chat_stream: Optional["ChatStream"],
) -> str:
"""
构建聊天历史总览块
从 chat_stream 获取历史消息,格式化为可读的聊天记录
类似于 AFC 的已读历史板块
"""
if not chat_stream:
return "(暂无聊天记录)"
try:
from src.chat.utils.chat_message_builder import build_readable_messages_with_id
from src.chat.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat
from src.common.data_models.database_data_model import DatabaseMessages
stream_context = chat_stream.context
# 获取已读消息
history_messages = stream_context.history_messages if stream_context else []
if not history_messages:
# 如果内存中没有历史消息,从数据库加载
fallback_messages_dicts = await get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id,
timestamp=time.time(),
limit=30, # 限制数量,私聊不需要太多
)
history_messages = [
DatabaseMessages(**msg_dict) for msg_dict in fallback_messages_dicts
]
if not history_messages:
return "(暂无聊天记录)"
# 构建可读消息
chat_content, _ = await build_readable_messages_with_id(
messages=[msg.flatten() for msg in history_messages[-30:]], # 最多30条
timestamp_mode="normal_no_YMD",
truncate=False,
show_actions=False,
)
return chat_content if chat_content else "(暂无聊天记录)"
except Exception as e:
logger.warning(f"构建聊天历史块失败: {e}")
return "(获取聊天记录失败)"
async def _build_activity_stream(
self,
session: KokoroSession,
@@ -285,6 +340,12 @@ class PromptBuilder:
"""构建当前情况描述"""
current_time = datetime.now().strftime("%Y年%m月%d%H:%M")
# 如果之前没有设置等待时间max_wait_seconds == 0视为 new_message
if situation_type in ("reply_in_time", "reply_late"):
max_wait = session.waiting_config.max_wait_seconds
if max_wait <= 0:
situation_type = "new_message"
if situation_type == "new_message":
return await global_prompt_manager.format_prompt(
PROMPT_NAMES["situation_new_message"],

View File

@@ -28,10 +28,15 @@ KFC_V2_MAIN_PROMPT = Prompt(
## 4. 当前情况
{current_situation}
## 5. 你可以做的事情
## 5. 聊天历史总览
以下是你和 {user_name} 的聊天记录,帮助你更好地理解对话上下文:
{chat_history_block}
## 6. 你可以做的事情
{available_actions}
## 6. 你的回复格式
## 7. 你的回复格式
{output_format}
""",
)

View File

@@ -69,7 +69,7 @@ async def generate_response(
success, raw_response, reasoning, model_name = await llm_api.generate_with_model(
prompt=prompt,
model_config=replyer_config,
request_type="kokoro_flow_chatter_v2",
request_type="kokoro_flow_chatter",
)
if not success:

View File

@@ -263,7 +263,7 @@ class SessionManager:
def __init__(
self,
data_dir: str = "data/kokoro_flow_chatter_v2/sessions",
data_dir: str = "data/kokoro_flow_chatter/sessions",
max_session_age_days: int = 30,
):
if hasattr(self, "_initialized") and self._initialized:

View File

@@ -140,14 +140,7 @@ class PokeAction(BaseAction):
# === 基本信息(必须填写)===
action_name = "poke_user"
action_description = """可以让你戳其他用户,为互动增添一份小小的乐趣。
判定条件:
1. **互动时机**: 这是一个有趣的互动方式,可以在想提醒某人,或者单纯想开个玩笑时使用。
2. **用户请求**: 当用户明确要求使用戳一戳时。
3. **上下文需求**: 当上下文明确需要你戳一个或多个人时。
4. **频率与情绪**: 如果最近已经戳过,或者感觉对方情绪不高,请避免使用,不要打扰到别人哦。
请根据上述规则,回答“是”或“否”。"""
action_description = "可以让你戳其他用户,为互动增添一份小小的乐趣。"
activation_type = ActionActivationType.ALWAYS
parallel_action = True