Merge branch 'PFC-test' of https://github.com/Plutor-05/MaiBot.git into PFC-test

pull/937/head
Plutor-05 2025-05-08 21:59:24 +08:00
commit 18f3864693
16 changed files with 276 additions and 310 deletions

View File

@ -6,56 +6,6 @@ from types import ModuleType
from pathlib import Path
from dotenv import load_dotenv
"""
日志颜色说明:
1. 主程序(Main)
浅黄色标题 | 浅黄色消息
2. 海马体(Memory)
浅黄色标题 | 浅黄色消息
3. PFC(前额叶皮质)
浅绿色标题 | 浅绿色消息
4. 心情(Mood)
品红色标题 | 品红色消息
5. 工具使用(Tool)
品红色标题 | 品红色消息
6. 关系(Relation)
浅品红色标题 | 浅品红色消息
7. 配置(Config)
浅青色标题 | 浅青色消息
8. 麦麦大脑袋
浅绿色标题 | 浅绿色消息
9. 在干嘛
青色标题 | 青色消息
10. 麦麦组织语言
浅绿色标题 | 浅绿色消息
11. 见闻(Chat)
浅蓝色标题 | 绿色消息
12. 表情包(Emoji)
橙色标题 | 橙色消息 fg #FFD700
13. 子心流
13. 其他模块
模块名标题 | 对应颜色消息
注意:
1. 级别颜色遵循loguru默认配置
2. 可通过环境变量修改日志级别
"""
# 加载 .env 文件
env_path = Path(__file__).resolve().parent.parent.parent / ".env"

View File

@ -276,7 +276,7 @@ class BotConfig:
enable_pfc_reply_checker: bool = True # 是否开启PFC回复检查
# idle_conversation
enable_idle_conversation: bool = False # 是否启用 pfc 主动发言(未完善)
enable_idle_conversation: bool = False # 是否启用 pfc 主动发言
idle_check_interval: int = 10 # 检查间隔10分钟检查一次
min_idle_time: int = 7200 # 最短无活动时间2小时 (7200秒)
max_idle_time: int = 18000 # 最长无活动时间5小时 (18000秒)
@ -493,7 +493,6 @@ class BotConfig:
"llm_heartflow",
"llm_PFC_action_planner",
"llm_PFC_chat",
"llm_PFC_reply_checker",
"llm_PFC_relationship_eval",
]
@ -666,7 +665,7 @@ class BotConfig:
config.talk_allowed_private = set(str(user) for user in experimental_config.get("talk_allowed_private", []))
if config.INNER_VERSION in SpecifierSet(">=1.1.0"):
config.enable_pfc_chatting = experimental_config.get("pfc_chatting", config.enable_pfc_chatting)
if config.INNER_VERSION in SpecifierSet(">=1.1.0"):
if config.INNER_VERSION in SpecifierSet(">=1.6.2"):
config.enable_pfc_reply_checker = experimental_config.get(
"enable_pfc_reply_checker", config.enable_pfc_reply_checker
)
@ -674,7 +673,7 @@ class BotConfig:
def idle_conversation(parent: dict):
idle_conversation_config = parent["idle_conversation"]
if config.INNER_VERSION in SpecifierSet(">=1.6.3"):
if config.INNER_VERSION in SpecifierSet(">=1.6.2"):
config.enable_idle_conversation = idle_conversation_config.get(
"enable_idle_conversation", config.enable_idle_conversation
)
@ -717,7 +716,7 @@ class BotConfig:
"chat": {"func": chat, "support": ">=1.6.0", "necessary": False},
"normal_chat": {"func": normal_chat, "support": ">=1.6.0", "necessary": False},
"focus_chat": {"func": focus_chat, "support": ">=1.6.0", "necessary": False},
"idle_conversation": {"func": idle_conversation, "support": ">=1.6.3", "necessary": False},
"idle_conversation": {"func": idle_conversation, "support": ">=1.6.2", "necessary": False},
}
# 原地修改,将 字符串版本表达式 转换成 版本对象

View File

@ -1,4 +1,4 @@
from typing import Optional, Dict, List, Set
from typing import Optional, Dict, Set
import asyncio
import time
import random
@ -7,11 +7,10 @@ from datetime import datetime
from src.common.logger_manager import get_logger
from src.config.config import global_config
from src.plugins.models.utils_model import LLMRequest
from src.plugins.utils.prompt_builder import Prompt, global_prompt_manager
from src.plugins.utils.prompt_builder import global_prompt_manager
from src.plugins.person_info.person_info import person_info_manager
from src.plugins.utils.chat_message_builder import build_readable_messages
from ...schedule.schedule_generator import bot_schedule
from ....config.config import global_config
from ..chat_observer import ChatObserver
from ..message_sender import DirectMessageSender
from src.plugins.chat.chat_stream import ChatStream
@ -23,32 +22,33 @@ install(extra_lines=3)
logger = get_logger("pfc_idle_chat")
class IdleChat:
"""主动聊天组件(测试中)
在以下条件都满足时触发主动聊天
1. 当前没有任何活跃的对话实例
2. 在指定的活动时间内7:00-23:00
3. 根据关系值动态调整触发概率
4. 上次触发后已经过了足够的冷却时间
"""
# 单例模式实现
_instances: Dict[str, 'IdleChat'] = {}
_instances: Dict[str, "IdleChat"] = {}
# 全局共享状态,用于跟踪未回复的用户
_pending_replies: Dict[str, float] = {} # 用户名 -> 发送时间
_tried_users: Set[str] = set() # 已尝试过的用户集合
_global_lock = asyncio.Lock() # 保护共享状态的全局锁
@classmethod
def get_instance(cls, stream_id: str, private_name: str) -> 'IdleChat':
def get_instance(cls, stream_id: str, private_name: str) -> "IdleChat":
"""获取IdleChat实例单例模式
Args:
stream_id: 聊天流ID
private_name: 私聊用户名称
Returns:
IdleChat: IdleChat实例
"""
@ -59,13 +59,13 @@ class IdleChat:
cls._instances[key].start()
logger.info(f"[私聊][{private_name}]创建新的IdleChat实例并启动")
return cls._instances[key]
@classmethod
async def register_user_response(cls, private_name: str) -> None:
"""注册用户已回复
当用户回复消息时调用此方法将用户从待回复列表中移除
Args:
private_name: 私聊用户名称
"""
@ -73,52 +73,52 @@ class IdleChat:
if private_name in cls._pending_replies:
del cls._pending_replies[private_name]
logger.info(f"[私聊][{private_name}]已回复主动聊天消息,从待回复列表中移除")
@classmethod
async def get_next_available_user(cls) -> Optional[str]:
"""获取下一个可用于主动聊天的用户
优先选择未尝试过的用户其次是已尝试但超时未回复的用户
Returns:
Optional[str]: 下一个可用的用户名如果没有则返回None
"""
async with cls._global_lock:
current_time = time.time()
timeout_threshold = 7200 # 2小时未回复视为超时
# 清理超时未回复的用户
for user, send_time in list(cls._pending_replies.items()):
if current_time - send_time > timeout_threshold:
logger.info(f"[私聊][{user}]超过{timeout_threshold}秒未回复,标记为超时")
del cls._pending_replies[user]
# 获取所有实例中的用户
all_users = set()
for key in cls._instances:
user = key.split(':', 1)[0]
user = key.split(":", 1)[0]
all_users.add(user)
# 优先选择未尝试过的用户
untried_users = all_users - cls._tried_users
if untried_users:
next_user = random.choice(list(untried_users))
cls._tried_users.add(next_user)
return next_user
# 如果所有用户都已尝试过,重置尝试集合,从头开始
if len(cls._tried_users) >= len(all_users):
cls._tried_users.clear()
logger.info(f"[私聊]所有用户都已尝试过,重置尝试列表")
logger.info("[私聊]所有用户都已尝试过,重置尝试列表")
# 随机选择一个不在待回复列表中的用户
available_users = all_users - set(cls._pending_replies.keys())
if available_users:
next_user = random.choice(list(available_users))
cls._tried_users.add(next_user)
return next_user
return None
def __init__(self, stream_id: str, private_name: str):
"""初始化主动聊天组件
@ -130,24 +130,19 @@ class IdleChat:
self.private_name = private_name
self.chat_observer = ChatObserver.get_instance(stream_id, private_name)
self.message_sender = DirectMessageSender(private_name)
# 添加异步锁,保护对共享变量的访问
self._lock: asyncio.Lock = asyncio.Lock()
# LLM请求对象用于生成主动对话内容
self.llm = LLMRequest(
model=global_config.llm_normal,
temperature=0.5,
max_tokens=500,
request_type="idle_chat"
)
self.llm = LLMRequest(model=global_config.llm_normal, temperature=0.5, max_tokens=500, request_type="idle_chat")
# 工作状态
self.active_instances_count: int = 0
self.last_trigger_time: float = time.time() - 1500 # 初始化时减少等待时间
self._running: bool = False
self._task: Optional[asyncio.Task] = None
# 配置参数 - 从global_config加载
self.min_cooldown = getattr(global_config, "min_idle_time", 7200) # 最短冷却时间默认2小时
self.max_cooldown = getattr(global_config, "max_idle_time", 18000) # 最长冷却时间默认5小时
@ -157,71 +152,70 @@ class IdleChat:
# 关系值相关
self.base_trigger_probability = 0.3 # 基础触发概率
self.relationship_factor = 0.0003 # 关系值影响因子
self.relationship_factor = 0.0003 # 关系值影响因子
def start(self) -> None:
"""启动主动聊天检测"""
# 检查是否启用了主动聊天功能
if not getattr(global_config, "ENABLE_IDLE_CONVERSATION", False):
logger.info(f"[私聊][{self.private_name}]主动聊天功能已禁用配置ENABLE_IDLE_CONVERSATION=False")
return
if self._running:
logger.debug(f"[私聊][{self.private_name}]主动聊天功能已在运行中")
return
self._running = True
self._task = asyncio.create_task(self._check_idle_loop())
logger.info(f"[私聊][{self.private_name}]启动主动聊天检测")
def stop(self) -> None:
"""停止主动聊天检测
"""
"""停止主动聊天检测"""
if not self._running:
return
self._running = False
if self._task:
self._task.cancel()
self._task = None
logger.info(f"[私聊][{self.private_name}]停止主动聊天检测")
async def increment_active_instances(self) -> None:
"""增加活跃实例计数
当创建新的对话实例时调用此方法
"""
async with self._lock:
self.active_instances_count += 1
logger.debug(f"[私聊][{self.private_name}]活跃实例数+1当前{self.active_instances_count}")
async def decrement_active_instances(self) -> None:
"""减少活跃实例计数
当对话实例结束时调用此方法
"""
async with self._lock:
self.active_instances_count = max(0, self.active_instances_count - 1)
logger.debug(f"[私聊][{self.private_name}]活跃实例数-1当前{self.active_instances_count}")
async def update_last_message_time(self, message_time: Optional[float] = None) -> None:
"""更新最后一条消息的时间
Args:
message_time: 消息时间戳如果为None则使用当前时间
"""
async with self._lock:
self.last_trigger_time = message_time or time.time()
logger.debug(f"[私聊][{self.private_name}]更新最后消息时间: {self.last_trigger_time:.2f}")
# 当用户发送消息时,也应该注册响应
await self.__class__.register_user_response(self.private_name)
def _is_active_hours(self) -> bool:
"""检查是否在活动时间内"""
current_hour = datetime.now().hour
return self.active_hours_start <= current_hour < self.active_hours_end
async def _should_trigger(self) -> bool:
"""检查是否应该触发主动聊天"""
async with self._lock:
@ -229,25 +223,27 @@ class IdleChat:
if self.active_instances_count < 0:
logger.warning(f"[私聊][{self.private_name}]检测到活跃实例数为负数重置为0")
self.active_instances_count = 0
# 检查是否有活跃实例
if self.active_instances_count > 0:
logger.debug(f"[私聊][{self.private_name}]存在活跃实例({self.active_instances_count}),不触发主动聊天")
return False
# 检查是否在活动时间内
if not self._is_active_hours():
logger.debug(f"[私聊][{self.private_name}]不在活动时间内,不触发主动聊天")
return False
# 检查冷却时间
current_time = time.time()
time_since_last_trigger = current_time - self.last_trigger_time
if time_since_last_trigger < self.min_cooldown:
time_left = self.min_cooldown - time_since_last_trigger
logger.debug(f"[私聊][{self.private_name}]冷却时间未到(已过{time_since_last_trigger:.0f}秒/需要{self.min_cooldown}秒),还需等待{time_left:.0f}秒,不触发主动聊天")
logger.debug(
f"[私聊][{self.private_name}]冷却时间未到(已过{time_since_last_trigger:.0f}秒/需要{self.min_cooldown}秒),还需等待{time_left:.0f}秒,不触发主动聊天"
)
return False
# 强制触发检查 - 如果超过最大冷却时间,增加触发概率
force_trigger = False
if time_since_last_trigger > self.max_cooldown * 2: # 如果超过最大冷却时间的两倍
@ -255,9 +251,11 @@ class IdleChat:
random_force = random.random()
force_trigger = random_force < force_probability
if force_trigger:
logger.info(f"[私聊][{self.private_name}]超过最大冷却时间({time_since_last_trigger:.0f}秒),强制触发主动聊天")
logger.info(
f"[私聊][{self.private_name}]超过最大冷却时间({time_since_last_trigger:.0f}秒),强制触发主动聊天"
)
return True
# 获取关系值
relationship_value = 0
try:
@ -270,10 +268,10 @@ class IdleChat:
# 先尝试通过昵称获取person_id
platform = "qq" # 默认平台
person_id = person_info_manager.get_person_id(platform, self.private_name)
# 如果通过昵称获取失败尝试通过stream_id解析
if not person_id:
parts = self.stream_id.split('_')
parts = self.stream_id.split("_")
if len(parts) >= 2 and parts[0] == "private":
user_id = parts[1]
platform = parts[2] if len(parts) >= 3 else "qq"
@ -284,7 +282,7 @@ class IdleChat:
person_id = person_info_manager.get_person_id(platform, user_id)
except Exception as e2:
logger.warning(f"[私聊][{self.private_name}]尝试获取person_id失败: {str(e2)}")
# 获取关系值
if person_id:
raw_value = await person_info_manager.get_value(person_id, "relationship_value")
@ -292,51 +290,61 @@ class IdleChat:
logger.debug(f"[私聊][{self.private_name}]成功获取关系值: {relationship_value}")
else:
logger.warning(f"[私聊][{self.private_name}]无法获取person_id使用默认关系值0")
# 使用PfcRepationshipTranslator获取关系描述
relationship_translator = PfcRepationshipTranslator(self.private_name)
relationship_level = relationship_translator._calculate_relationship_level_num(relationship_value, self.private_name)
relationship_level = relationship_translator._calculate_relationship_level_num(
relationship_value, self.private_name
)
# 基于关系等级调整触发概率
# 关系越好,主动聊天概率越高
level_probability_factors = [0.05, 0.1, 0.2, 0.3, 0.4, 0.5] # 每个等级对应的基础概率因子
base_probability = level_probability_factors[relationship_level]
# 基础概率因子
trigger_probability = base_probability
trigger_probability = max(0.05, min(0.6, trigger_probability)) # 限制在0.05-0.6之间
# 最大冷却时间调整 - 随着冷却时间增加,逐渐增加触发概率
if time_since_last_trigger > self.max_cooldown:
# 计算额外概率 - 每超过最大冷却时间的10%增加1%的概率最多增加30%
extra_time_factor = min(0.3, (time_since_last_trigger - self.max_cooldown) / (self.max_cooldown * 10))
extra_time_factor = min(
0.3, (time_since_last_trigger - self.max_cooldown) / (self.max_cooldown * 10)
)
trigger_probability += extra_time_factor
logger.debug(f"[私聊][{self.private_name}]超过标准冷却时间,额外增加概率: +{extra_time_factor:.2f}")
# 随机判断是否触发
random_value = random.random()
should_trigger = random_value < trigger_probability
logger.debug(f"[私聊][{self.private_name}]触发概率计算: 基础({base_probability:.2f}) + 关系值({relationship_value})影响 = {trigger_probability:.2f},随机值={random_value:.2f}, 结果={should_trigger}")
logger.debug(
f"[私聊][{self.private_name}]触发概率计算: 基础({base_probability:.2f}) + 关系值({relationship_value})影响 = {trigger_probability:.2f},随机值={random_value:.2f}, 结果={should_trigger}"
)
# 如果决定触发,记录详细日志
if should_trigger:
logger.info(f"[私聊][{self.private_name}]决定触发主动聊天: 触发概率={trigger_probability:.2f}, 距上次已过{time_since_last_trigger:.0f}")
logger.info(
f"[私聊][{self.private_name}]决定触发主动聊天: 触发概率={trigger_probability:.2f}, 距上次已过{time_since_last_trigger:.0f}"
)
return should_trigger
except Exception as e:
logger.error(f"[私聊][{self.private_name}]获取关系值失败: {str(e)}")
logger.error(traceback.format_exc())
# 即使获取关系值失败,仍有一个基础的几率触发
# 这确保即使数据库有问题,主动聊天功能仍然可用
base_fallback_probability = 0.1 # 较低的基础几率
random_fallback = random.random()
fallback_trigger = random_fallback < base_fallback_probability
if fallback_trigger:
logger.info(f"[私聊][{self.private_name}]获取关系值失败,使用后备触发机制: 概率={base_fallback_probability:.2f}, 决定={fallback_trigger}")
logger.info(
f"[私聊][{self.private_name}]获取关系值失败,使用后备触发机制: 概率={base_fallback_probability:.2f}, 决定={fallback_trigger}"
)
return fallback_trigger
async def _check_idle_loop(self) -> None:
"""检查空闲状态的循环"""
try:
@ -346,16 +354,16 @@ class IdleChat:
# 如果禁用了功能,等待一段时间后再次检查配置
await asyncio.sleep(60) # 每分钟检查一次配置变更
continue
# 检查当前用户是否应该触发主动聊天
should_trigger = await self._should_trigger()
# 如果当前用户不触发,检查是否有其他用户已经超时未回复
if not should_trigger:
async with self.__class__._global_lock:
current_time = time.time()
pending_timeout = 1800 # 30分钟未回复检查
# 检查此用户是否在等待回复列表中
if self.private_name in self.__class__._pending_replies:
logger.debug(f"[私聊][{self.private_name}]当前用户在等待回复列表中,不进行额外检查")
@ -365,12 +373,12 @@ class IdleChat:
for user, send_time in self.__class__._pending_replies.items():
if current_time - send_time > pending_timeout:
timed_out_users.append(user)
# 如果有超时未回复的用户,尝试找下一个用户
if timed_out_users:
logger.info(f"[私聊]发现{len(timed_out_users)}个用户超过{pending_timeout}秒未回复")
next_user = await self.__class__.get_next_available_user()
if next_user and next_user != self.private_name:
logger.info(f"[私聊]选择下一个用户[{next_user}]进行主动聊天")
# 查找该用户的实例并触发聊天
@ -380,7 +388,7 @@ class IdleChat:
# 触发该实例的主动聊天
asyncio.create_task(instance._initiate_chat())
break
# 如果当前用户应该触发主动聊天
if should_trigger:
try:
@ -388,7 +396,7 @@ class IdleChat:
# 更新上次触发时间
async with self._lock:
self.last_trigger_time = time.time()
# 将此用户添加到等待回复列表中
async with self.__class__._global_lock:
self.__class__._pending_replies[self.private_name] = time.time()
@ -397,12 +405,12 @@ class IdleChat:
except Exception as e:
logger.error(f"[私聊][{self.private_name}]执行主动聊天过程出错: {str(e)}")
logger.error(traceback.format_exc())
# 等待下一次检查
check_interval = self.check_interval # 使用配置的检查间隔
logger.debug(f"[私聊][{self.private_name}]等待{check_interval}秒后进行下一次主动聊天检查")
await asyncio.sleep(check_interval)
except asyncio.CancelledError:
logger.debug(f"[私聊][{self.private_name}]主动聊天检测任务被取消")
except Exception as e:
@ -412,24 +420,25 @@ class IdleChat:
if self._running:
logger.info(f"[私聊][{self.private_name}]尝试重新启动主动聊天检测")
self._task = asyncio.create_task(self._check_idle_loop())
async def _get_chat_stream(self) -> Optional[ChatStream]:
"""获取聊天流实例"""
try:
# 尝试从全局聊天管理器获取现有的聊天流
from src.plugins.chat.chat_stream import chat_manager
existing_chat_stream = chat_manager.get_stream(self.stream_id)
if existing_chat_stream:
logger.debug(f"[私聊][{self.private_name}]从chat_manager找到现有聊天流")
return existing_chat_stream
# 如果没有现有聊天流,则创建新的
logger.debug(f"[私聊][{self.private_name}]未找到现有聊天流,创建新聊天流")
# 创建用户信息对象
user_info = UserInfo(
user_id=self.private_name, # 使用私聊用户的ID
user_nickname=self.private_name, # 使用私聊用户的名称
platform="qq"
platform="qq",
)
# 创建聊天流
new_stream = ChatStream(self.stream_id, "qq", user_info)
@ -441,23 +450,19 @@ class IdleChat:
logger.error(f"[私聊][{self.private_name}]创建/获取聊天流失败: {str(e)}")
logger.error(traceback.format_exc())
return None
async def _initiate_chat(self) -> None:
"""生成并发送主动聊天消息"""
try:
# 获取聊天历史记录
messages = self.chat_observer.get_cached_messages(limit=12)
chat_history_text = await build_readable_messages(
messages,
replace_bot_name=True,
merge_messages=False,
timestamp_mode="relative",
read_mark=0.0
messages, replace_bot_name=True, merge_messages=False, timestamp_mode="relative", read_mark=0.0
)
# 获取关系信息
from src.plugins.person_info.relationship_manager import relationship_manager
# 获取关系值
relationship_value = 0
try:
@ -468,23 +473,25 @@ class IdleChat:
relationship_value = relationship_manager.ensure_float(raw_value, person_id)
except Exception as e:
logger.warning(f"[私聊][{self.private_name}]获取关系值失败,使用默认值: {e}")
# 使用PfcRepationshipTranslator获取关系描述
relationship_translator = PfcRepationshipTranslator(self.private_name)
full_relationship_text = await relationship_translator.translate_relationship_value_to_text(relationship_value)
full_relationship_text = await relationship_translator.translate_relationship_value_to_text(
relationship_value
)
# 提取纯关系描述(去掉"你们的关系是:"前缀)
relationship_description = "普通" # 默认值
if "" in full_relationship_text:
relationship_description = full_relationship_text.split("")[1].replace("", "")
if global_config.ENABLE_SCHEDULE_GEN:
schedule_prompt = await global_prompt_manager.format_prompt(
"schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False)
)
"schedule_prompt", schedule_info=bot_schedule.get_current_num_task(num=1, time_info=False)
)
else:
schedule_prompt = ""
# 构建提示词
current_time = datetime.now().strftime("%H:%M")
prompt = f"""你是{global_config.BOT_NICKNAME}
@ -500,14 +507,11 @@ class IdleChat:
请直接输出一条消息不要有任何额外的解释或引导文字
消息内容尽量简短
"""
# 生成回复
logger.debug(f"[私聊][{self.private_name}]开始生成主动聊天内容")
try:
content, _ = await asyncio.wait_for(
self.llm.generate_response_async(prompt),
timeout=30
)
content, _ = await asyncio.wait_for(self.llm.generate_response_async(prompt), timeout=30)
logger.debug(f"[私聊][{self.private_name}]成功生成主动聊天内容: {content}")
except asyncio.TimeoutError:
logger.error(f"[私聊][{self.private_name}]生成主动聊天内容超时")
@ -516,34 +520,30 @@ class IdleChat:
logger.error(f"[私聊][{self.private_name}]生成主动聊天内容失败: {str(llm_err)}")
logger.error(traceback.format_exc())
return
# 清理结果
content = content.strip()
content = content.strip("\"'")
if not content:
logger.error(f"[私聊][{self.private_name}]生成的主动聊天内容为空")
return
# 获取聊天流
chat_stream = await self._get_chat_stream()
if not chat_stream:
logger.error(f"[私聊][{self.private_name}]无法获取有效的聊天流,取消发送主动消息")
return
# 发送消息
try:
logger.debug(f"[私聊][{self.private_name}]准备发送主动聊天消息: {content}")
await self.message_sender.send_message(
chat_stream=chat_stream,
content=content,
reply_to_message=None
)
await self.message_sender.send_message(chat_stream=chat_stream, content=content, reply_to_message=None)
logger.info(f"[私聊][{self.private_name}]成功主动发起聊天: {content}")
except Exception as e:
logger.error(f"[私聊][{self.private_name}]发送主动聊天消息失败: {str(e)}")
logger.error(traceback.format_exc())
except Exception as e:
logger.error(f"[私聊][{self.private_name}]主动发起聊天过程中发生未预期的错误: {str(e)}")
logger.error(traceback.format_exc())
logger.error(traceback.format_exc())

View File

@ -3,13 +3,13 @@ import traceback
from typing import Tuple, Optional, Dict, Any, List
from src.common.logger_manager import get_logger
from src.individuality.individuality import Individuality
# from src.individuality.individuality import Individuality
from src.plugins.utils.chat_message_builder import build_readable_messages
from ..models.utils_model import LLMRequest
from ...config.config import global_config
from src.config.config import global_config
# 确保导入路径正确
from .pfc_utils import get_items_from_json, retrieve_contextual_info
from .pfc_utils import get_items_from_json
from .chat_observer import ChatObserver
from .observation_info import ObservationInfo
from .conversation_info import ConversationInfo
@ -22,26 +22,21 @@ logger = get_logger("pfc_action_planner")
# Prompt(1): 首次回复或非连续回复时的决策 Prompt
PROMPT_INITIAL_REPLY = """
当前时间{current_time_str}
{persona_text}
现在你正在和{sender_name}在QQ上私聊
你和对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
请根据以下所有信息审慎且灵活的决策下一步行动可以回复可以倾听可以调取知识甚至可以屏蔽对方
现在{persona_text}正在与{sender_name}在qq上私聊
他们的关系是{relationship_text}
{persona_text}现在的心情是是{current_emotion_text}
你现在需要操控{persona_text}根据以下所有信息灵活合理的决策{persona_text}的下一步行动需要符合正常人的社交流程可以回复可以倾听甚至可以屏蔽对方
当前对话目标
{goals_str}
最近行动历史概要
{action_history_summary}
你想起来的相关知识
{retrieved_knowledge_str}
上一次行动的详细情况和结果
{last_action_context}
时间和超时提示
{time_since_last_bot_message_info}{timeout_context}
最近的对话记录(包括你已成功发送的消息 新收到的消息)
{chat_history_text}
你的回忆
{retrieved_memory_str}
{spam_warning_info}
@ -56,7 +51,7 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
请以JSON格式输出你的决策
{{
"action": "选择的行动类型 (必须是上面列表中的一个)",
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的)"
"reason": "选择该行动的原因 "
}}
注意请严格按照JSON格式输出不要包含任何其他内容"""
@ -64,26 +59,21 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
# Prompt(2): 上一次成功回复后,决定继续发言时的决策 Prompt
PROMPT_FOLLOW_UP = """
当前时间{current_time_str}
{persona_text}
现在你正在和{sender_name}在QQ上私聊**并且刚刚你已经回复了对方**
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
请根据以下所有信息审慎且灵活的决策下一步行动可以继续发送新消息可以等待可以倾听可以调取知识甚至可以屏蔽对方
现在{persona_text}正在与{sender_name}在qq上私聊**并且刚刚{persona_text}已经回复了对方**
他们的关系是{relationship_text}
{persona_text}现在的心情是是{current_emotion_text}
你现在需要操控{persona_text}根据以下所有信息灵活合理的决策{persona_text}的下一步行动需要符合正常人的社交流程可以发送新消息可以等待可以倾听可以结束对话甚至可以屏蔽对方
当前对话目标
{goals_str}
最近行动历史概要
{action_history_summary}
你想起来的相关知识
{retrieved_knowledge_str}
上一次行动的详细情况和结果
{last_action_context}
时间和超时提示
{time_since_last_bot_message_info}{timeout_context}
最近的对话记录(包括你已成功发送的消息 新收到的消息)
{chat_history_text}
你的回忆
{retrieved_memory_str}
{spam_warning_info}
@ -99,7 +89,7 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
请以JSON格式输出你的决策
{{
"action": "选择的行动类型 (必须是上面列表中的一个)",
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的。)"
"reason": "选择该行动的原因"
}}
注意请严格按照JSON格式输出不要包含任何其他内容"""
@ -107,19 +97,22 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
# 新增Prompt(3): 决定是否在结束对话前发送告别语
PROMPT_END_DECISION = """
当前时间{current_time_str}
{persona_text}刚刚你决定结束一场 QQ 私聊
现在{persona_text}{sender_name}刚刚结束了一场qq私聊
他们的关系是{relationship_text}
你现在需要操控{persona_text}根据以下所有信息灵活合理的决策{persona_text}的下一步行动需要符合正常人的社交流程
你们之前的聊天记录
他们之前的聊天记录
{chat_history_text}
你觉得们的对话已经完整结束了吗有时候在对话自然结束后再说点什么可能会有点奇怪但有时也可能需要一条简短的消息来圆满结束
如果觉得确实有必要再发一条简短自然符合你人设的告别消息比如 "好,下次再聊~" "嗯,先这样吧"就输出 "yes"
你觉得们的对话已经完整结束了吗有时候在对话自然结束后再说点什么可能会有点奇怪但有时也可能需要一条简短的消息来圆满结束
如果觉得确实有必要再发一条简短自然的告别消息比如 "好,下次再聊~" "嗯,先这样吧"就输出 "yes"
如果觉得当前状态下直接结束对话更好没有必要再发消息就输出 "no"
请以 JSON 格式输出你的选择
{{
"say_bye": "yes/no",
"reason": "选择 yes 或 no 的原因和内心想法 (简要说明)"
"reason": "选择 yes 或 no 的原因和 (简要说明)"
}}
注意请严格按照 JSON 格式输出不要包含任何其他内容"""
@ -127,27 +120,21 @@ PROMPT_END_DECISION = """
# Prompt(4): 当 reply_generator 决定不发送消息后的反思决策 Prompt
PROMPT_REFLECT_AND_ACT = """
当前时间{current_time_str}
{persona_text}
现在你正在和{sender_name}在QQ上私聊
你与对方的关系是{relationship_text}
你现在的心情是{current_emotion_text}
刚刚你本来想发一条新消息但是想了想你决定不发了
请根据以下所有信息审慎且灵活的决策下一步行动可以等待可以倾听可以结束对话甚至可以屏蔽对方
现在{persona_text}正在与{sender_name}在qq上私聊刚刚{persona_text}打算发一条新消息想了想还是不发了
他们的关系是{relationship_text}
{persona_text}现在的心情是是{current_emotion_text}
你现在需要操控{persona_text}根据以下所有信息灵活合理的决策{persona_text}的下一步行动需要符合正常人的社交流程可以等待可以倾听可以结束对话甚至可以屏蔽对方
当前对话目标
{goals_str}
最近行动历史概要
{action_history_summary}
你想起来的相关知识
{retrieved_knowledge_str}
上一次行动的详细情况和结果
{last_action_context}
时间和超时提示
{time_since_last_bot_message_info}{timeout_context}
最近的对话记录(包括你已成功发送的消息 新收到的消息)
{chat_history_text}
你的回忆
{retrieved_memory_str}
{spam_warning_info}
@ -162,12 +149,11 @@ block_and_ignore: 更加极端的结束对话方式,直接结束对话并在
请以JSON格式输出你的决策
{{
"action": "选择的行动类型 (必须是上面列表中的一个)",
"reason": "选择该行动的详细原因 (必须有解释你是如何根据“上一次行动结果”、“对话记录”和自身设定人设做出合理判断的。)"
"reason": "选择该行动的原因"
}}
注意请严格按照JSON格式输出不要包含任何其他内容"""
class ActionPlanner:
"""行动规划器"""
@ -195,7 +181,7 @@ class ActionPlanner:
raise
# 获取个性化信息和机器人名称
self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3)
# self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3)
self.name = global_config.BOT_NICKNAME
# 获取 ChatObserver 实例 (单例模式)
self.chat_observer = ChatObserver.get_instance(stream_id, private_name)
@ -228,21 +214,21 @@ class ActionPlanner:
goals_str = self._build_goals_string(conversation_info)
chat_history_text = await self._build_chat_history_text(observation_info)
# 获取 sender_name, relationship_text, current_emotion_text
sender_name_str = getattr(observation_info, "sender_name", "对方") # 从 observation_info 获取
if not sender_name_str:
sender_name_str = "对方" # 再次确保有默认值
sender_name_str = getattr(observation_info, 'sender_name', '对方') # 从 observation_info 获取
if not sender_name_str: sender_name_str = '对方' # 再次确保有默认值
relationship_text_str = getattr(conversation_info, "relationship_text", "你们还不熟悉。")
current_emotion_text_str = getattr(conversation_info, "current_emotion_text", "心情平静。")
relationship_text_str = getattr(conversation_info, 'relationship_text', '你们还不熟悉。')
current_emotion_text_str = getattr(conversation_info, 'current_emotion_text', '心情平静。')
persona_text = f"你的名字是{self.name}{self.personality_info}"
persona_text = f"{self.name}"
action_history_summary, last_action_context = self._build_action_history_context(conversation_info)
retrieved_memory_str, retrieved_knowledge_str = await retrieve_contextual_info(
chat_history_text, self.private_name
)
logger.info(
f"[私聊][{self.private_name}] (ActionPlanner) 检索完成。记忆: {'' if '回忆起' in retrieved_memory_str else ''} / 知识: {'' if retrieved_knowledge_str and '无相关知识' not in retrieved_knowledge_str and '出错' not in retrieved_knowledge_str else ''}"
)
# retrieved_memory_str, retrieved_knowledge_str = await retrieve_contextual_info(
# chat_history_text, self.private_name
# )
# logger.info(
# f"[私聊][{self.private_name}] (ActionPlanner) 检索完成。记忆: {'有' if '回忆起' in retrieved_memory_str else '无'} / 知识: {'有' if retrieved_knowledge_str and '无相关知识' not in retrieved_knowledge_str and '出错' not in retrieved_knowledge_str else '无'}"
# )
except Exception as prep_err:
logger.error(f"[私聊][{self.private_name}] 准备 Prompt 输入时出错: {prep_err}")
logger.error(traceback.format_exc())
@ -250,16 +236,14 @@ class ActionPlanner:
# --- 2. 选择并格式化 Prompt ---
try:
if use_reflect_prompt: # 新增的判断
if use_reflect_prompt: # 新增的判断
prompt_template = PROMPT_REFLECT_AND_ACT
log_msg = "使用 PROMPT_REFLECT_AND_ACT (反思决策)"
# 对于 PROMPT_REFLECT_AND_ACT它不包含 send_new_message 选项,所以 spam_warning_message 中的相关提示可以调整或省略
# 但为了保持占位符填充的一致性,我们仍然计算它
spam_warning_message = ""
if conversation_info.my_message_count > 5: # 这里的 my_message_count 仍有意义,表示之前连续发送了多少
spam_warning_message = (
f"⚠️【警告】**你之前已连续发送{str(conversation_info.my_message_count)}条消息!请谨慎决策。**"
)
if conversation_info.my_message_count > 5: # 这里的 my_message_count 仍有意义,表示之前连续发送了多少
spam_warning_message = f"⚠️【警告】**你之前已连续发送{str(conversation_info.my_message_count)}条消息!请谨慎决策。**"
elif conversation_info.my_message_count > 2:
spam_warning_message = f"💬【提示】**你之前已连续发送{str(conversation_info.my_message_count)}条消息。请注意保持对话平衡。**"
@ -275,12 +259,12 @@ class ActionPlanner:
else:
prompt_template = PROMPT_INITIAL_REPLY
log_msg = "使用 PROMPT_INITIAL_REPLY (首次/非连续回复决策)"
spam_warning_message = "" # 初始回复时通常不需要刷屏警告
spam_warning_message = "" # 初始回复时通常不需要刷屏警告
logger.debug(f"[私聊][{self.private_name}] {log_msg}")
current_time_value = "获取时间失败"
if observation_info and hasattr(observation_info, "current_time_str") and observation_info.current_time_str:
if observation_info and hasattr(observation_info, 'current_time_str') and observation_info.current_time_str:
current_time_value = observation_info.current_time_str
if spam_warning_message:
@ -294,13 +278,13 @@ class ActionPlanner:
time_since_last_bot_message_info=time_since_last_bot_message_info,
timeout_context=timeout_context,
chat_history_text=chat_history_text if chat_history_text.strip() else "还没有聊天记录。",
retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。",
retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。",
# retrieved_memory_str=retrieved_memory_str if retrieved_memory_str else "无相关记忆。",
# retrieved_knowledge_str=retrieved_knowledge_str if retrieved_knowledge_str else "无相关知识。",
current_time_str=current_time_value,
spam_warning_info=spam_warning_message,
sender_name=sender_name_str,
relationship_text=relationship_text_str,
current_emotion_text=current_emotion_text_str,
current_emotion_text=current_emotion_text_str
)
logger.debug(f"[私聊][{self.private_name}] 发送到LLM的最终提示词:\n------\n{prompt}\n------")
except KeyError as fmt_key_err:
@ -347,7 +331,11 @@ class ActionPlanner:
):
time_str_for_end_decision = observation_info.current_time_str
final_action, final_reason = await self._handle_end_conversation_decision(
persona_text, chat_history_text, initial_reason, time_str_for_end_decision
persona_text,
chat_history_text, initial_reason,
time_str_for_end_decision,
sender_name_str=sender_name_str,
relationship_text_str=relationship_text_str
)
except Exception as end_dec_err:
logger.error(f"[私聊][{self.private_name}] 处理结束对话决策时出错: {end_dec_err}")
@ -372,7 +360,7 @@ class ActionPlanner:
"block_and_ignore",
"say_goodbye",
]
valid_actions_reflect = [ # PROMPT_REFLECT_AND_ACT 的动作
valid_actions_reflect = [ # PROMPT_REFLECT_AND_ACT 的动作
"wait",
"listening",
"rethink_goal",
@ -513,7 +501,9 @@ class ActionPlanner:
)
logger.debug(f"[私聊][{self.private_name}] 向 LLM 追加了 {other_unread_count} 条未读消息。")
else:
chat_history_text += "\n--- 以上均为已读消息,未读消息均已处理完毕 ---\n"
chat_history_text += (
f"\n--- 以上均为已读消息,未读消息均已处理完毕 ---\n"
)
except AttributeError as e:
logger.warning(f"[私聊][{self.private_name}] 构建聊天记录文本时属性错误: {e}")
chat_history_text = "[获取聊天记录时出错]\n"
@ -571,13 +561,11 @@ class ActionPlanner:
# --- Helper method for handling end_conversation decision ---
async def _handle_end_conversation_decision(
self, persona_text: str, chat_history_text: str, initial_reason: str, current_time_str: str
self, persona_text: str, chat_history_text: str, initial_reason: str, current_time_str: str, sender_name_str: str, relationship_text_str: str
) -> Tuple[str, str]:
"""处理结束对话前的告别决策"""
logger.info(f"[私聊][{self.private_name}] 初步规划结束对话,进入告别决策...")
end_decision_prompt = PROMPT_END_DECISION.format(
persona_text=persona_text, chat_history_text=chat_history_text, current_time_str=current_time_str
)
end_decision_prompt = PROMPT_END_DECISION.format(persona_text=persona_text, chat_history_text=chat_history_text,current_time_str=current_time_str,sender_name = sender_name_str, relationship_text = relationship_text_str)
logger.debug(f"[私聊][{self.private_name}] 发送到LLM的结束决策提示词:\n------\n{end_decision_prompt}\n------")
llm_start_time = time.time()
end_content, _ = await self.llm.generate_response_async(end_decision_prompt)

View File

@ -246,7 +246,7 @@ async def handle_action(
is_suitable = True
check_reason = "ReplyChecker 已通过配置关闭"
need_replan_from_checker = False
logger.info(f"{log_prefix} [配置关闭] ReplyChecker 已跳过,默认回复为合适。")
logger.debug(f"{log_prefix} [配置关闭] ReplyChecker 已跳过,默认回复为合适。")
# 处理检查结果
if not is_suitable:
@ -295,7 +295,7 @@ async def handle_action(
# 后续的 plan 循环会检测到这个 "done_no_reply" 状态并使用反思 prompt
elif is_suitable: # 适用于 direct_reply 或 (send_new_message 且 RG决定发送并通过检查)
logger.info(f"[私聊][{conversation_instance.private_name}] 动作 '{action}': 找到合适的回复,准备发送。")
logger.debug(f"[私聊][{conversation_instance.private_name}] 动作 '{action}': 找到合适的回复,准备发送。")
# conversation_info.last_reply_rejection_reason = None # 已在循环内清除
# conversation_info.last_rejected_reply_content = None
conversation_instance.generated_reply = generated_content_for_check_or_send # 使用检查通过的内容
@ -311,7 +311,7 @@ async def handle_action(
action_successful = True
final_status = "done" # 明确设置 final_status
final_reason = "成功发送" # 明确设置 final_reason
logger.info(f"[私聊][{conversation_instance.private_name}] 动作 '{action}': 成功发送回复.")
logger.debug(f"[私聊][{conversation_instance.private_name}] 动作 '{action}': 成功发送回复.")
# --- 新增:将机器人发送的消息添加到 ObservationInfo 的 chat_history ---
if (
@ -334,7 +334,7 @@ async def handle_action(
observation_info.chat_history.append(bot_message_dict)
observation_info.chat_history_count = len(observation_info.chat_history)
logger.debug(
f"[私聊][{conversation_instance.private_name}] 机器人发送的消息已添加到 chat_history。当前历史数: {observation_info.chat_history_count}"
f"[私聊][{conversation_instance.private_name}] {global_config.BOT_NICKNAME}发送的消息已添加到 chat_history。当前历史数: {observation_info.chat_history_count}"
)
# 可选:如果 chat_history 过长,进行修剪 (例如保留最近N条)
@ -398,13 +398,13 @@ async def handle_action(
# 如果是 direct_reply 且规划期间有他人新消息,则下次不追问
if other_new_msg_count_during_planning > 0 and action == "direct_reply":
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] 因规划期间收到 {other_new_msg_count_during_planning} 条他人新消息,下一轮强制使用【初始回复】逻辑。"
)
conversation_info.last_successful_reply_action = None
# conversation_info.my_message_count 不在此处重置,因为它刚发了一条
elif action == "direct_reply" or action == "send_new_message": # 成功发送后
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] 成功执行 '{action}', 下一轮【允许】使用追问逻辑。"
)
conversation_info.last_successful_reply_action = action
@ -413,7 +413,7 @@ async def handle_action(
if conversation_info: # 再次确认
conversation_info.current_instance_message_count += 1
logger.debug(
f"[私聊][{conversation_instance.private_name}] 实例消息计数(机器人发送后)增加到: {conversation_info.current_instance_message_count}"
f"[私聊][{conversation_instance.private_name}] 实例消息计数({global_config.BOT_NICKNAME}发送后)增加到: {conversation_info.current_instance_message_count}"
)
if conversation_instance.relationship_updater: # 确保存在

View File

@ -114,11 +114,11 @@ class ChatObserver:
logger.debug(
f"[私聊][{self.private_name}] 消息已添加到 ChatObserver 缓存,当前缓存大小: {len(self.message_cache)}"
)
# 检查是否用户发送的消息(而非机器人自己)
try:
from .PFC_idle.idle_chat import IdleChat
# 获取消息的发送者
user_info = message.get("user_info", {})
if user_info and str(user_info.get("user_id")) != str(global_config.BOT_QQ):

View File

@ -134,7 +134,7 @@ class Conversation:
observation_info=self.observation_info,
chat_observer_for_history=self.chat_observer,
)
logger.info(f"[私聊][{self.private_name}] 最终关系评估已调用。")
logger.debug(f"[私聊][{self.private_name}] 最终关系评估已调用。")
except Exception as e_final_rel:
logger.error(f"[私聊][{self.private_name}] 调用最终关系评估时出错: {e_final_rel}")
logger.error(traceback.format_exc())
@ -145,12 +145,12 @@ class Conversation:
if self.idle_chat:
# 减少活跃实例计数而不是停止IdleChat
await self.idle_chat.decrement_active_instances()
logger.info(f"[私聊][{self.private_name}] 已减少IdleChat活跃实例计数")
logger.debug(f"[私聊][{self.private_name}] 已减少IdleChat活跃实例计数")
if self.observation_info and self.chat_observer:
self.observation_info.unbind_from_chat_observer()
if self.mood_mng and hasattr(self.mood_mng, "stop_mood_update") and self.mood_mng._running: # type: ignore
self.mood_mng.stop_mood_update() # type: ignore
logger.info(f"[私聊][{self.private_name}] MoodManager 后台更新已停止。")
logger.debug(f"[私聊][{self.private_name}] MoodManager 后台更新已停止。")
self._initialized = False # 标记为未初始化
logger.info(f"[私聊][{self.private_name}] 对话实例 {self.stream_id} 已停止。")

View File

@ -42,7 +42,7 @@ async def load_initial_history(conversation_instance: "Conversation"):
return
try:
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] 为 {conversation_instance.stream_id} 加载初始聊天记录..."
)
# 从聊天核心获取原始消息列表
@ -99,10 +99,7 @@ async def load_initial_history(conversation_instance: "Conversation"):
conversation_instance.chat_observer.last_message_time = (
conversation_instance.observation_info.last_message_time
)
if (
conversation_instance.idle_chat
and conversation_instance.observation_info.last_message_time
):
if conversation_instance.idle_chat and conversation_instance.observation_info.last_message_time:
# 更新空闲计时器的起始时间
await conversation_instance.idle_chat.update_last_message_time(
conversation_instance.observation_info.last_message_time
@ -135,7 +132,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
# return
# conversation_instance._initializing_flag_from_manager = True # 标记开始初始化
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] (Initializer) 开始初始化对话实例核心组件: {conversation_instance.stream_id}"
)
@ -152,12 +149,12 @@ async def initialize_core_components(conversation_instance: "Conversation"):
conversation_instance.relationship_translator = PfcRepationshipTranslator(
private_name=conversation_instance.private_name
)
logger.info(f"[私聊][{conversation_instance.private_name}] (Initializer) PfcRelationship 初始化完成。")
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) PfcRelationship 初始化完成。")
conversation_instance.emotion_updater = PfcEmotionUpdater(
private_name=conversation_instance.private_name, bot_name=global_config.BOT_NICKNAME
)
logger.info(f"[私聊][{conversation_instance.private_name}] (Initializer) PfcEmotion 初始化完成。")
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) PfcEmotion 初始化完成。")
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) 初始化 GoalAnalyzer...")
conversation_instance.goal_analyzer = GoalAnalyzer(
@ -197,7 +194,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
conversation_instance.stream_id, conversation_instance.private_name
)
await conversation_instance.idle_chat.increment_active_instances()
logger.info(f"[私聊][{conversation_instance.private_name}] (Initializer) IdleChat实例已获取并增加活跃计数")
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) IdleChat实例已获取并增加活跃计数")
# 2. 初始化信息存储和观察组件
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) 获取 ChatObserver 实例...")
@ -238,7 +235,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
conversation_instance.conversation_info.person_id = person_id_tuple[0] # 第一个元素是 person_id
private_platform_str = person_id_tuple[1]
private_user_id_str = person_id_tuple[2]
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] (Initializer) 获取到 person_id: {conversation_instance.conversation_info.person_id} for {private_platform_str}:{private_user_id_str}"
)
else:
@ -254,7 +251,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
if conversation_instance.idle_chat:
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) 启动 IdleChat...")
# 不需要再次启动,只需确保已初始化
logger.info(f"[私聊][{conversation_instance.private_name}] (Initializer) IdleChat实例已初始化")
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) IdleChat实例已初始化")
if (
conversation_instance.mood_mng
@ -262,11 +259,11 @@ async def initialize_core_components(conversation_instance: "Conversation"):
and not conversation_instance.mood_mng._running
): # type: ignore
conversation_instance.mood_mng.start_mood_update(update_interval=global_config.mood_update_interval) # type: ignore
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] (Initializer) MoodManager 已启动后台更新,间隔: {global_config.mood_update_interval} 秒。"
)
elif conversation_instance.mood_mng and conversation_instance.mood_mng._running: # type: ignore
logger.info(f"[私聊][{conversation_instance.private_name}] (Initializer) MoodManager 已在运行中。")
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) MoodManager 已在运行中。")
else:
logger.warning(
f"[私聊][{conversation_instance.private_name}] (Initializer) MoodManager 未能启动,相关功能可能受限。"
@ -294,7 +291,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
numeric_relationship_value
)
)
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] (Initializer) 初始化时加载关系文本: {conversation_instance.conversation_info.relationship_text}"
)
except Exception as e_init_rel:
@ -308,7 +305,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
conversation_instance.conversation_info.current_emotion_text = (
conversation_instance.mood_mng.get_prompt()
) # type: ignore
logger.info(
logger.debug(
f"[私聊][{conversation_instance.private_name}] (Initializer) 初始化时加载情绪文本: {conversation_instance.conversation_info.current_emotion_text}"
)
except Exception as e_init_emo:

View File

@ -28,7 +28,7 @@ async def run_conversation_loop(conversation_instance: "Conversation"):
核心的规划与行动循环 (PFC Loop)
之前是 Conversation 类中的 _plan_and_action_loop 方法
"""
logger.info(f"[私聊][{conversation_instance.private_name}] 进入 run_conversation_loop 循环。")
logger.debug(f"[私聊][{conversation_instance.private_name}] 进入 run_conversation_loop 循环。")
if not conversation_instance._initialized:
logger.error(f"[私聊][{conversation_instance.private_name}] 尝试在未初始化状态下运行规划循环,退出。")
@ -69,10 +69,7 @@ async def run_conversation_loop(conversation_instance: "Conversation"):
conversation_instance.ignore_until_timestamp
and loop_iter_start_time < conversation_instance.ignore_until_timestamp
):
if (
conversation_instance.idle_chat
and conversation_instance.idle_chat._running
):
if conversation_instance.idle_chat and conversation_instance.idle_chat._running:
# 不直接停止服务,改为暂时忽略此用户
# 虽然我们仍然可以通过active_instances_count来决定是否触发主动聊天
# 但为了安全起见,我们只记录一个日志

View File

@ -297,7 +297,7 @@ class ObservationInfo:
if new_count < original_count:
self.new_messages_count = new_count
logger.info(
logger.debug(
f"[私聊][{self.private_name}] 移除了未处理的消息 (ID: {message_id_to_delete}), 当前未处理数: {self.new_messages_count}"
)
self.update_changed()
@ -384,7 +384,7 @@ class ObservationInfo:
self.new_messages_count = len(self.unprocessed_messages)
self.chat_history_count = len(self.chat_history)
logger.info(
logger.debug(
f"[私聊][{self.private_name}] 已清理 {cleared_count} 条消息 (IDs: {message_ids_to_clear}),剩余未处理 {self.new_messages_count} 条,当前历史记录 {self.chat_history_count} 条。"
)

View File

@ -24,7 +24,7 @@ class PfcEmotionUpdater:
# LLM 实例 (根据 global_config.llm_summary 配置)
llm_config_summary = getattr(global_config, "llm_summary", None)
if llm_config_summary and isinstance(llm_config_summary, dict):
logger.info(f"[私聊][{self.private_name}] 使用 llm_summary 配置初始化情绪判断LLM。")
logger.debug(f"[私聊][{self.private_name}] 使用 llm_summary 配置初始化情绪判断LLM。")
self.llm = LLMRequest(
model=llm_config_summary,
temperature=llm_config_summary.get(
@ -102,11 +102,11 @@ class PfcEmotionUpdater:
and detected_emotion_word in self.mood_mng.emotion_map
):
self.mood_mng.update_mood_from_emotion(detected_emotion_word, intensity=self.EMOTION_UPDATE_INTENSITY)
logger.info(
logger.debug(
f"[私聊][{self.private_name}] 基于事件 '{event_description}',情绪已更新为倾向于 '{detected_emotion_word}'。当前心情: {self.mood_mng.current_mood.text}"
)
elif detected_emotion_word == "无变化":
logger.info(f"[私聊][{self.private_name}] 基于事件 '{event_description}'LLM判断情绪无显著变化。")
logger.debug(f"[私聊][{self.private_name}] 基于事件 '{event_description}'LLM判断情绪无显著变化。")
else:
logger.warning(
f"[私聊][{self.private_name}] LLM返回了未知的情绪词 '{detected_emotion_word}' 或未返回有效词,情绪未主动更新。"

View File

@ -9,7 +9,7 @@ from src.plugins.person_info.relationship_manager import (
from src.plugins.utils.chat_message_builder import build_readable_messages
from src.plugins.PFC.observation_info import ObservationInfo
from src.plugins.PFC.conversation_info import ConversationInfo
from src.plugins.PFC.pfc_utils import get_items_from_json
from src.plugins.PFC.pfc_utils import get_items_from_json, adjust_relationship_value_nonlinear
from src.config.config import global_config # 导入全局配置 (向上两级到 src/, 再到 config)
@ -121,7 +121,7 @@ class PfcRelationshipUpdater:
请输出一个JSON对象包含一个 "adjustment" 字段其值为一个介于 -{self.REL_INCREMENTAL_MAX_CHANGE} +{self.REL_INCREMENTAL_MAX_CHANGE} 之间的整数代表关系值的变化
例如{{ "adjustment": 3 }}如果对话内容不明确或难以判断请倾向于输出较小的调整值如0, 1, -1"""
adjustment_val = self.REL_INCREMENTAL_DEFAULT_CHANGE
raw_adjustment_val = self.REL_INCREMENTAL_DEFAULT_CHANGE
try:
logger.debug(f"[私聊][{self.private_name}] 增量关系评估Prompt:\n{relationship_prompt}")
content, _ = await self.llm.generate_response_async(relationship_prompt)
@ -136,13 +136,17 @@ class PfcRelationshipUpdater:
)
raw_adjustment = result.get("adjustment", self.REL_INCREMENTAL_DEFAULT_CHANGE)
if not isinstance(raw_adjustment, (int, float)):
adjustment_val = self.REL_INCREMENTAL_DEFAULT_CHANGE
raw_adjustment_val = self.REL_INCREMENTAL_DEFAULT_CHANGE
else:
adjustment_val = float(raw_adjustment)
adjustment_val = max(-self.REL_INCREMENTAL_MAX_CHANGE, min(self.REL_INCREMENTAL_MAX_CHANGE, adjustment_val))
raw_adjustment_val = float(raw_adjustment)
raw_adjustment_val = max(
-self.REL_INCREMENTAL_MAX_CHANGE, min(self.REL_INCREMENTAL_MAX_CHANGE, raw_adjustment_val)
)
except Exception as e:
logger.error(f"[私聊][{self.private_name}] 增量关系评估LLM调用或解析失败: {e}")
adjustment_val = await adjust_relationship_value_nonlinear(current_relationship_value, raw_adjustment_val)
new_relationship_value = max(-1000.0, min(1000.0, current_relationship_value + adjustment_val))
await self.person_info_mng.update_one_field(
conversation_info.person_id, "relationship_value", new_relationship_value
@ -202,7 +206,7 @@ class PfcRelationshipUpdater:
请输出一个JSON对象包含一个 "final_adjustment" 字段其值为一个整数代表关系值的变化量例如可以是 -{self.REL_FINAL_MAX_CHANGE} +{self.REL_FINAL_MAX_CHANGE} 之间的一个值
请大胆评估但也要合理"""
adjustment_val = self.REL_FINAL_DEFAULT_CHANGE
raw_adjustment_val = self.REL_FINAL_DEFAULT_CHANGE
try:
logger.debug(f"[私聊][{self.private_name}] 最终关系评估Prompt:\n{relationship_prompt}")
content, _ = await self.llm.generate_response_async(relationship_prompt)
@ -217,13 +221,17 @@ class PfcRelationshipUpdater:
)
raw_adjustment = result.get("final_adjustment", self.REL_FINAL_DEFAULT_CHANGE)
if not isinstance(raw_adjustment, (int, float)):
adjustment_val = self.REL_FINAL_DEFAULT_CHANGE
raw_adjustment_val = self.REL_FINAL_DEFAULT_CHANGE
else:
adjustment_val = float(raw_adjustment)
adjustment_val = max(-self.REL_FINAL_MAX_CHANGE, min(self.REL_FINAL_MAX_CHANGE, adjustment_val))
raw_adjustment_val = float(raw_adjustment)
raw_adjustment_val = max(
-self.REL_INCREMENTAL_MAX_CHANGE, min(self.REL_INCREMENTAL_MAX_CHANGE, raw_adjustment_val)
)
except Exception as e:
logger.error(f"[私聊][{self.private_name}] 最终关系评估LLM调用或解析失败: {e}")
adjustment_val = await adjust_relationship_value_nonlinear(current_relationship_value, raw_adjustment_val)
new_relationship_value = max(-1000.0, min(1000.0, current_relationship_value + adjustment_val))
await self.person_info_mng.update_one_field(
conversation_info.person_id, "relationship_value", new_relationship_value

View File

@ -7,6 +7,7 @@ from src.plugins.memory_system.Hippocampus import HippocampusManager
from src.plugins.heartFC_chat.heartflow_prompt_builder import prompt_builder # 确认 prompt_builder 的导入路径
from src.plugins.chat.chat_stream import ChatStream
from ..person_info.person_info import person_info_manager
import math
logger = get_logger("pfc_utils")
@ -273,7 +274,7 @@ async def get_person_id(private_name: str, chat_stream: ChatStream):
if chat_stream.user_info:
private_user_id_str = str(chat_stream.user_info.user_id)
private_platform_str = chat_stream.user_info.platform
logger.info(
logger.debug(
f"[私聊][{private_name}] 从 ChatStream 获取到私聊对象信息: ID={private_user_id_str}, Platform={private_platform_str}, Name={private_nickname_str}"
)
elif chat_stream.group_info is None and private_name:
@ -308,3 +309,33 @@ async def get_person_id(private_name: str, chat_stream: ChatStream):
f"[私聊][{private_name}] 未能确定私聊对象的 user_id 或 platform无法获取 person_id。将在收到消息后尝试。"
)
return None # 返回 None 表示失败
async def adjust_relationship_value_nonlinear(old_value: float, raw_adjustment: float) -> float:
# 限制 old_value 范围
old_value = max(-1000, min(1000, old_value))
value = raw_adjustment
if old_value >= 0:
if value >= 0:
value = value * math.cos(math.pi * old_value / 2000)
if old_value > 500:
rdict = await person_info_manager.get_specific_value_list("relationship_value", lambda x: x > 700)
high_value_count = len(rdict)
if old_value > 700:
value *= 3 / (high_value_count + 2)
else:
value *= 3 / (high_value_count + 3)
elif value < 0:
value = value * math.exp(old_value / 2000)
else:
value = 0
else:
if value >= 0:
value = value * math.exp(old_value / 2000)
elif value < 0:
value = value * math.cos(math.pi * old_value / 2000)
else:
value = 0
return value

View File

@ -42,7 +42,9 @@ class ReplyChecker:
对于非重复消息: (True, "消息内容未与机器人历史发言重复。", False)
"""
if not self.bot_qq_str:
logger.error(f"[私聊][{self.private_name}] ReplyChecker: BOT_QQ 未配置,无法检查机器人自身消息。")
logger.error(
f"[私聊][{self.private_name}] ReplyChecker: BOT_QQ 未配置,无法检查{global_config.BOT_NICKNAME}自身消息。"
)
return True, "BOT_QQ未配置跳过重复检查。", False # 无法检查则默认通过
if len(reply) <= 4:
@ -64,11 +66,13 @@ class ReplyChecker:
historical_message_text = msg_dict.get("processed_plain_text", "")
# <--- 新增详细对比日志 --- START --->
logger.debug(
f"[私聊][{self.private_name}] ReplyChecker: 历史记录 #{i} (机器人): '{historical_message_text}' (长度 {len(historical_message_text)})"
f"[私聊][{self.private_name}] ReplyChecker: 历史记录 #{i} ({global_config.BOT_NICKNAME}): '{historical_message_text}' (长度 {len(historical_message_text)})"
)
if reply == historical_message_text:
logger.warning(f"[私聊][{self.private_name}] ReplyChecker: !!! 精确匹配成功 !!!")
logger.warning(f"[私聊][{self.private_name}] ReplyChecker 检测到机器人自身重复消息: '{reply}'")
logger.warning(
f"[私聊][{self.private_name}] ReplyChecker 检测到{global_config.BOT_NICKNAME}自身重复消息: '{reply}'"
)
match_found = True # <--- 标记找到
return (False, "机器人尝试发送重复消息", False)
# <--- 新增详细对比日志 --- END --->

View File

@ -260,7 +260,7 @@ class ReplyGenerator:
f"请根据此提示调整你的新回复,确保内容新颖,不要重复你已经说过的话。\n"
f"------\n"
)
logger.info(
logger.debug(
f"[私聊][{self.private_name}] (ReplyGenerator) 检测到自身复读,将加入特定警告到 Prompt:\n"
f" 内容: {last_content}"
)
@ -273,7 +273,7 @@ class ReplyGenerator:
f"请根据【消息内容】和【失败原因】调整你的新回复,避免重复之前的错误。\n"
f"------\n"
)
logger.info(
logger.debug(
f"[私聊][{self.private_name}] (ReplyGenerator) 检测到上次回复失败信息,将加入 Prompt:\n"
f" 内容: {last_content}\n"
f" 原因: {last_reason}"

View File

@ -1,5 +1,5 @@
[inner]
version = "1.6.5"
version = "1.6.2"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请在修改后将version的值进行变更
@ -191,7 +191,7 @@ pfc_chatting = false # 是否启用PFC聊天该功能仅作用于私聊
enable_pfc_reply_checker = true # 是否启用 PFC 的回复检查器
[idle_conversation]
enable_idle_conversation = false
enable_idle_conversation = false # 是否启用 pfc 主动发言
idle_check_interval = 10 # 检查间隔10分钟检查一次
min_idle_time = 7200 # 最短无活动时间2小时 (7200秒)
max_idle_time = 18000 # 最长无活动时间5小时 (18000秒)
@ -291,21 +291,13 @@ temp = 0.3
pri_in = 2
pri_out = 8
#PFC检查模型
[model.llm_PFC_reply_checker]
name = "Pro/deepseek-ai/DeepSeek-V3"
provider = "SILICONFLOW"
pri_in = 2
pri_out = 8
# PFC 关系评估LLM
[model.llm_PFC_relationship_eval]
name = "Pro/deepseek-ai/DeepSeek-V3" # 或者其他你认为适合判断任务的模型
provider = "SILICONFLOW"
temp = 0.4 # 判断任务建议温度稍低
max_tokens = 512 # 根据Prompt长度和期望输出来调整
pri_in = 2 # 价格信息(可选)
pri_out = 8 # 价格信息(可选)
temp = 0.4
pri_in = 2
pri_out = 8
#以下模型暂时没有使用!!
#以下模型暂时没有使用!!