fix(chat): 优化breaking模式下的兴趣值累积逻辑

重构heartFC_chat中的消息处理机制,使用累积兴趣值替代最近三次记录来判断是否进入breaking模式。主要变更包括:

- 将breaking模式判断基于累积兴趣值而非最近3次记录
- 在消息成功处理时重置累积兴趣值
- 调整阈值计算方式,使用聊天频率进行动态调整
- 修复send_api中的消息查找函数,提高回复消息匹配准确性

这些改动提高了对话节奏控制的稳定性,使breaking模式触发更加合理。
This commit is contained in:
Windpicker-owo
2025-09-03 22:19:00 +08:00
parent 18a57d0a74
commit efe81fa346
5 changed files with 105 additions and 137 deletions

View File

@@ -128,7 +128,7 @@ class CycleProcessor:
x0 = 1.0 # 控制曲线中心点
return 1.0 / (1.0 + math.exp(-k * (interest_val - x0)))
normal_mode_probability = calculate_normal_mode_probability(interest_value) / global_config.chat.get_current_talk_frequency(self.context.stream_id)
normal_mode_probability = calculate_normal_mode_probability(interest_value) * 0.5 / global_config.chat.get_current_talk_frequency(self.context.stream_id)
# 根据概率决定使用哪种模式
if random.random() < normal_mode_probability:

View File

@@ -255,10 +255,12 @@ class HeartFChatting:
for message in recent_messages:
await self.cycle_processor.observe(interest_value = interest_value)
# 如果成功观察,增加能量值
# 如果成功观察,增加能量值并重置累积兴趣值
if has_new_messages:
self.context.energy_value += 1 / global_config.chat.focus_value
logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f}")
# 重置累积兴趣值,因为消息已经被成功处理
self.context.breaking_accumulated_interest = 0.0
logger.info(f"{self.context.log_prefix} 能量值增加,当前能量值:{self.context.energy_value:.1f},重置累积兴趣值")
self._check_focus_exit()
@@ -384,23 +386,20 @@ class HeartFChatting:
if self.context.no_reply_consecutive <= 3:
self.context.focus_energy = 1
else:
# 计算最近三次记录的兴趣度总和
total_recent_interest = sum(self.recent_interest_records)
# 获取当前聊天频率和意愿系数
talk_frequency = global_config.chat.get_current_talk_frequency(self.context.stream_id)
# 使用累积兴趣值而不是最近3次的记录
total_interest = self.context.breaking_accumulated_interest
# 计算调整后的阈值
adjusted_threshold = 3 / talk_frequency
adjusted_threshold = 1 / global_config.chat.get_current_talk_frequency(self.context.stream_id)
logger.info(f"{self.context.log_prefix} 最近三次兴趣度总和: {total_recent_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}")
logger.info(f"{self.context.log_prefix} 累积兴趣值: {total_interest:.2f}, 调整后阈值: {adjusted_threshold:.2f}")
# 如果兴趣度总和小于阈值进入breaking形式
if total_recent_interest < adjusted_threshold:
logger.info(f"{self.context.log_prefix} 兴趣度不足进入breaking形式")
# 如果累积兴趣值小于阈值进入breaking形式
if total_interest < adjusted_threshold:
logger.info(f"{self.context.log_prefix} 累积兴趣度不足进入breaking形式")
self.context.focus_energy = random.randint(3, 6)
else:
logger.info(f"{self.context.log_prefix} 兴趣度充足")
logger.info(f"{self.context.log_prefix} 累积兴趣度充足使用waiting形式")
self.context.focus_energy = 1
async def _should_process_messages(self, new_message: List[Dict[str, Any]]) -> tuple[bool,float]:
@@ -409,36 +408,48 @@ class HeartFChatting:
根据当前循环模式和消息内容决定是否继续处理
"""
new_message_count = len(new_message)
# talk_frequency = global_config.chat.get_current_talk_frequency(self.context.chat_stream.stream_id)
modified_exit_count_threshold = self.context.focus_energy / global_config.chat.focus_value
modified_exit_interest_threshold = 3 / global_config.chat.focus_value
total_interest = 0.0
talk_frequency = global_config.chat.get_current_talk_frequency(self.context.chat_stream.stream_id)
modified_exit_count_threshold = self.context.focus_energy * 0.5 / talk_frequency
modified_exit_interest_threshold = 1.5 / talk_frequency
# 计算当前批次消息的兴趣值
batch_interest = 0.0
for msg_dict in new_message:
interest_value = msg_dict.get("interest_value", 0.0)
if msg_dict.get("processed_plain_text", ""):
total_interest += interest_value
batch_interest += interest_value
# 在breaking形式下累积所有消息的兴趣值
if new_message_count > 0:
self.context.breaking_accumulated_interest += batch_interest
total_interest = self.context.breaking_accumulated_interest
else:
total_interest = self.context.breaking_accumulated_interest
if new_message_count >= modified_exit_count_threshold:
# 记录兴趣度到列表
self.recent_interest_records.append(total_interest)
# 重置累积兴趣值,因为已经达到了消息数量阈值
self.context.breaking_accumulated_interest = 0.0
logger.info(
f"{self.context.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待"
f"{self.context.log_prefix} 累计消息数量达到{new_message_count}条(>{modified_exit_count_threshold:.1f}),结束等待,累积兴趣值: {total_interest:.2f}"
)
# logger.info(self.context.last_read_time)
# logger.info(new_message)
return True,total_interest/new_message_count
# 检查累计兴趣值
if new_message_count > 0:
# 只在兴趣值变化时输出log
if not hasattr(self, "_last_accumulated_interest") or total_interest != self._last_accumulated_interest:
logger.info(f"{self.context.log_prefix} breaking形式当前累兴趣值: {total_interest:.2f}, 专注度: {global_config.chat.focus_value:.1f}")
logger.info(f"{self.context.log_prefix} breaking形式当前累兴趣值: {total_interest:.2f}, 专注度: {global_config.chat.focus_value:.1f}")
self._last_accumulated_interest = total_interest
if total_interest >= modified_exit_interest_threshold:
# 记录兴趣度到列表
self.recent_interest_records.append(total_interest)
# 重置累积兴趣值,因为已经达到了兴趣值阈值
self.context.breaking_accumulated_interest = 0.0
logger.info(
f"{self.context.log_prefix} 累计兴趣值达到{total_interest:.2f}(>{modified_exit_interest_threshold:.1f}),结束等待"
)
@@ -447,7 +458,7 @@ class HeartFChatting:
# 每10秒输出一次等待状态
if int(time.time() - self.context.last_read_time) > 0 and int(time.time() - self.context.last_read_time) % 10 == 0:
logger.info(
f"{self.context.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累兴趣{total_interest:.1f},继续等待..."
f"{self.context.log_prefix} 已等待{time.time() - self.last_read_time:.0f}秒,累计{new_message_count}条消息,累兴趣{total_interest:.1f},继续等待..."
)
await asyncio.sleep(0.5)

View File

@@ -48,6 +48,9 @@ class HfcContext:
self.last_message_time = time.time()
self.last_read_time = time.time() - 10
# 从聊天流恢复breaking累积兴趣值
self.breaking_accumulated_interest = getattr(self.chat_stream, 'breaking_accumulated_interest', 0.0)
self.action_manager = ActionManager()
self.running: bool = False
@@ -63,6 +66,8 @@ class HfcContext:
self.focus_energy = 1
self.no_reply_consecutive = 0
self.total_interest = 0.0
# breaking形式下的累积兴趣值
self.breaking_accumulated_interest = 0.0
# 引用HeartFChatting实例以便其他组件可以调用其方法
self.chat_instance = None
@@ -73,3 +78,4 @@ class HfcContext:
self.chat_stream.sleep_pressure = self.sleep_pressure
self.chat_stream.focus_energy = self.focus_energy
self.chat_stream.no_reply_consecutive = self.no_reply_consecutive
self.chat_stream.breaking_accumulated_interest = self.breaking_accumulated_interest

View File

@@ -480,10 +480,18 @@ class ActionPlanner:
mentioned_bonus = "\n- 有人提到你或者at你"
if mode == ChatMode.FOCUS:
no_action_block = """重要说明:
no_action_block = """
- 'no_reply' 表示不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
动作no_reply
动作描述:不进行回复,等待合适的回复时机
- 当你刚刚发送了消息没有人回复时选择no_reply
- 当你一次发送了太多消息为了避免打扰聊天节奏选择no_reply
{{
"action": "no_reply",
"reason":"不回复的原因"
}}
"""
else: # NORMAL Mode
no_action_block = """重要说明:

View File

@@ -51,6 +51,55 @@ logger = get_logger("send_api")
# 适配器命令响应等待池
_adapter_response_pool: Dict[str, asyncio.Future] = {}
def message_dict_to_message_recv(message_dict: Dict[str, Any]) -> Optional[MessageRecv]:
"""查找要回复的消息
Args:
message_dict: 消息字典
Returns:
Optional[MessageRecv]: 找到的消息如果没找到则返回None
"""
# 构建MessageRecv对象
user_info = {
"platform": message_dict.get("user_platform", ""),
"user_id": message_dict.get("user_id", ""),
"user_nickname": message_dict.get("user_nickname", ""),
"user_cardname": message_dict.get("user_cardname", ""),
}
group_info = {}
if message_dict.get("chat_info_group_id"):
group_info = {
"platform": message_dict.get("chat_info_group_platform", ""),
"group_id": message_dict.get("chat_info_group_id", ""),
"group_name": message_dict.get("chat_info_group_name", ""),
}
format_info = {"content_format": "", "accept_format": ""}
template_info = {"template_items": {}}
message_info = {
"platform": message_dict.get("chat_info_platform", ""),
"message_id": message_dict.get("message_id"),
"time": message_dict.get("time"),
"group_info": group_info,
"user_info": user_info,
"additional_config": message_dict.get("additional_config"),
"format_info": format_info,
"template_info": template_info,
}
message_dict = {
"message_info": message_info,
"raw_message": message_dict.get("processed_plain_text"),
"processed_plain_text": message_dict.get("processed_plain_text"),
}
message_recv = MessageRecv(message_dict)
logger.info(f"[SendAPI] 找到匹配的回复消息,发送者: {message_dict.get('user_nickname', '')}")
return message_recv
def put_adapter_response(request_id: str, response_data: dict) -> None:
"""将适配器响应放入响应池"""
@@ -139,7 +188,7 @@ async def _send_to_target(
message_segment = Seg(type=message_type, data=content) # type: ignore
if reply_to_message:
anchor_message = MessageRecv(message_dict=reply_to_message)
anchor_message = message_dict_to_message_recv(message_dict=reply_to_message)
anchor_message.update_chat_stream(target_stream)
reply_to_platform_id = (
f"{anchor_message.message_info.platform}:{anchor_message.message_info.user_info.user_id}"
@@ -184,112 +233,6 @@ async def _send_to_target(
return False
async def _find_reply_message(target_stream, reply_to: str) -> Optional[MessageRecv]:
# sourcery skip: inline-variable, use-named-expression
"""查找要回复的消息
Args:
target_stream: 目标聊天流
reply_to: 回复格式,如"发送者:消息内容""发送者:消息内容"
Returns:
Optional[MessageRecv]: 找到的消息如果没找到则返回None
"""
try:
# 解析reply_to参数
if ":" in reply_to:
parts = reply_to.split(":", 1)
elif "" in reply_to:
parts = reply_to.split("", 1)
else:
logger.warning(f"[SendAPI] reply_to格式不正确: {reply_to}")
return None
if len(parts) != 2:
logger.warning(f"[SendAPI] reply_to格式不正确: {reply_to}")
return None
sender = parts[0].strip()
text = parts[1].strip()
# 获取聊天流的最新20条消息
reverse_talking_message = get_raw_msg_before_timestamp_with_chat(
target_stream.stream_id,
time.time(), # 当前时间之前的消息
20, # 最新的20条消息
)
# 反转列表,使最新的消息在前面
reverse_talking_message = list(reversed(reverse_talking_message))
find_msg = None
for message in reverse_talking_message:
user_id = message["user_id"]
platform = message["chat_info_platform"]
person_id = get_person_info_manager().get_person_id(platform, user_id)
person_name = await get_person_info_manager().get_value(person_id, "person_name")
if person_name == sender:
translate_text = message["processed_plain_text"]
# 使用独立函数处理用户引用格式
translate_text = await replace_user_references_async(translate_text, platform)
similarity = difflib.SequenceMatcher(None, text, translate_text).ratio()
if similarity >= 0.9:
find_msg = message
break
if not find_msg:
logger.info("[SendAPI] 未找到匹配的回复消息")
return None
# 构建MessageRecv对象
user_info = {
"platform": find_msg.get("user_platform", ""),
"user_id": find_msg.get("user_id", ""),
"user_nickname": find_msg.get("user_nickname", ""),
"user_cardname": find_msg.get("user_cardname", ""),
}
group_info = {}
if find_msg.get("chat_info_group_id"):
group_info = {
"platform": find_msg.get("chat_info_group_platform", ""),
"group_id": find_msg.get("chat_info_group_id", ""),
"group_name": find_msg.get("chat_info_group_name", ""),
}
format_info = {"content_format": "", "accept_format": ""}
template_info = {"template_items": {}}
message_info = {
"platform": target_stream.platform,
"message_id": find_msg.get("message_id"),
"time": find_msg.get("time"),
"group_info": group_info,
"user_info": user_info,
"additional_config": find_msg.get("additional_config"),
"format_info": format_info,
"template_info": template_info,
}
message_dict = {
"message_info": message_info,
"raw_message": find_msg.get("processed_plain_text"),
"processed_plain_text": find_msg.get("processed_plain_text"),
}
find_rec_msg = MessageRecv(message_dict)
find_rec_msg.update_chat_stream(target_stream)
logger.info(f"[SendAPI] 找到匹配的回复消息,发送者: {sender}")
return find_rec_msg
except Exception as e:
logger.error(f"[SendAPI] 查找回复消息时出错: {e}")
traceback.print_exc()
return None
# =============================================================================
# 公共API函数 - 预定义类型的发送函数