better:重整配置,分离表达,聊天模式区分

重整配置文件路径,添加更多配置选项
分离了人设表达方式和学习到的表达方式
将聊天模式区分为normal focus和auto
This commit is contained in:
SengokuCola
2025-05-20 22:41:55 +08:00
parent 67569f1fa6
commit 25d9032e62
54 changed files with 387 additions and 482 deletions

View File

@@ -41,7 +41,7 @@ class APIBotConfig:
allow_focus_mode: bool # 是否允许专注聊天状态
base_normal_chat_num: int # 最多允许多少个群进行普通聊天
base_focused_chat_num: int # 最多允许多少个群进行专注聊天
chat.observation_context_size: int # 观察到的最长上下文大小
observation_context_size: int # 观察到的最长上下文大小
message_buffer: bool # 是否启用消息缓冲
ban_words: List[str] # 禁止词列表
ban_msgs_regex: List[str] # 禁止消息的正则表达式列表
@@ -128,7 +128,7 @@ class APIBotConfig:
llm_reasoning: Dict[str, Any] # 推理模型配置
llm_normal: Dict[str, Any] # 普通模型配置
llm_topic_judge: Dict[str, Any] # 主题判断模型配置
model.summary: Dict[str, Any] # 总结模型配置
summary: Dict[str, Any] # 总结模型配置
vlm: Dict[str, Any] # VLM模型配置
llm_heartflow: Dict[str, Any] # 心流模型配置
llm_observation: Dict[str, Any] # 观察模型配置
@@ -203,7 +203,7 @@ class APIBotConfig:
"llm_reasoning",
"llm_normal",
"llm_topic_judge",
"model.summary",
"summary",
"vlm",
"llm_heartflow",
"llm_observation",

View File

@@ -17,7 +17,7 @@ from src.manager.mood_manager import mood_manager
from src.chat.heart_flow.utils_chat import get_chat_type_and_target_info
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
import time
@@ -281,7 +281,6 @@ class DefaultExpressor:
in_mind_reply,
target_message,
) -> str:
individuality = Individuality.get_instance()
prompt_personality = individuality.get_prompt(x_person=0, level=2)
# Determine if it's a group chat
@@ -294,7 +293,7 @@ class DefaultExpressor:
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id,
timestamp=time.time(),
limit=global_config.chat.observation_context_size,
limit=global_config.focus_chat.observation_context_size,
)
chat_talking_prompt = await build_readable_messages(
message_list_before_now,

View File

@@ -36,24 +36,6 @@ def init_prompt() -> None:
"""
Prompt(learn_style_prompt, "learn_style_prompt")
personality_expression_prompt = """
{personality}
请从以上人设中总结出这个角色可能的语言风格
思考回复的特殊内容和情感
思考有没有特殊的梗,一并总结成语言风格
总结成如下格式的规律,总结的内容要详细,但具有概括性:
"xxx"时,可以"xxx", xxx不超过10个字
例如:
"表示十分惊叹"时,使用"我嘞个xxxx"
"表示讽刺的赞同,不想讲道理"时,使用"对对对"
"想说明某个观点,但懒得明说",使用"懂的都懂"
现在请你概括
"""
Prompt(personality_expression_prompt, "personality_expression_prompt")
learn_grammar_prompt = """
{chat_str}
@@ -278,44 +260,6 @@ class ExpressionLearner:
expressions.append((chat_id, situation, style))
return expressions
async def extract_and_store_personality_expressions(self):
"""
检查data/expression/personality目录不存在则创建。
用peronality变量作为chat_str调用LLM生成表达风格解析后count=100存储到expressions.json。
"""
dir_path = os.path.join("data", "expression", "personality")
os.makedirs(dir_path, exist_ok=True)
file_path = os.path.join(dir_path, "expressions.json")
# 构建prompt
prompt = await global_prompt_manager.format_prompt(
"personality_expression_prompt",
personality=global_config.personality.expression_style,
)
# logger.info(f"个性表达方式提取prompt: {prompt}")
try:
response, _ = await self.express_learn_model.generate_response_async(prompt)
except Exception as e:
logger.error(f"个性表达方式提取失败: {e}")
return
logger.info(f"个性表达方式提取response: {response}")
# chat_id用personality
expressions = self.parse_expression_response(response, "personality")
# 转为dict并count=100
result = []
for _, situation, style in expressions:
result.append({"situation": situation, "style": style, "count": 100})
# 超过50条时随机删除多余的只保留50条
if len(result) > 50:
remove_count = len(result) - 50
remove_indices = set(random.sample(range(len(result)), remove_count))
result = [item for idx, item in enumerate(result) if idx not in remove_indices]
with open(file_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
logger.info(f"已写入{len(result)}条表达到{file_path}")
init_prompt()

View File

@@ -3,7 +3,7 @@ import contextlib
import time
import traceback
from collections import deque
from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
from typing import List, Optional, Dict, Any, Deque
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.message_receive.chat_stream import chat_manager
from rich.traceback import install

View File

@@ -5,7 +5,6 @@ from ...config.config import global_config
from ..message_receive.message import MessageRecv
from ..message_receive.storage import MessageStorage
from ..utils.utils import is_mentioned_bot_in_message
from maim_message import Seg
from src.chat.heart_flow.heartflow import heartflow
from src.common.logger_manager import get_logger
from ..message_receive.chat_stream import chat_manager
@@ -79,26 +78,26 @@ async def _calculate_interest(message: MessageRecv) -> Tuple[float, bool]:
return interested_rate, is_mentioned
def _get_message_type(message: MessageRecv) -> str:
"""获取消息类型
# def _get_message_type(message: MessageRecv) -> str:
# """获取消息类型
Args:
message: 消息对象
# Args:
# message: 消息对象
Returns:
str: 消息类型
"""
if message.message_segment.type != "seglist":
return message.message_segment.type
# Returns:
# str: 消息类型
# """
# if message.message_segment.type != "seglist":
# return message.message_segment.type
if (
isinstance(message.message_segment.data, list)
and all(isinstance(x, Seg) for x in message.message_segment.data)
and len(message.message_segment.data) == 1
):
return message.message_segment.data[0].type
# if (
# isinstance(message.message_segment.data, list)
# and all(isinstance(x, Seg) for x in message.message_segment.data)
# and len(message.message_segment.data) == 1
# ):
# return message.message_segment.data[0].type
return "seglist"
# return "seglist"
def _check_ban_words(text: str, chat, userinfo) -> bool:
@@ -141,7 +140,7 @@ def _check_ban_regex(text: str, chat, userinfo) -> bool:
return False
class HeartFCProcessor:
class HeartFCMessageReceiver:
"""心流处理器,负责处理接收到的消息并计算兴趣度"""
def __init__(self):

View File

@@ -1,6 +1,6 @@
from src.config.config import global_config
from src.common.logger_manager import get_logger
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
from src.chat.person_info.relationship_manager import relationship_manager
@@ -103,7 +103,6 @@ class PromptBuilder:
return None
async def _build_prompt_normal(self, chat_stream, message_txt: str, sender_name: str = "某人") -> str:
individuality = Individuality.get_instance()
prompt_personality = individuality.get_prompt(x_person=2, level=2)
is_group_chat = bool(chat_stream.group_info)
@@ -112,7 +111,7 @@ class PromptBuilder:
who_chat_in_group = get_recent_group_speaker(
chat_stream.stream_id,
(chat_stream.user_info.platform, chat_stream.user_info.user_id) if chat_stream.user_info else None,
limit=global_config.chat.observation_context_size,
limit=global_config.focus_chat.observation_context_size,
)
elif chat_stream.user_info:
who_chat_in_group.append(
@@ -160,7 +159,7 @@ class PromptBuilder:
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id,
timestamp=time.time(),
limit=global_config.chat.observation_context_size,
limit=global_config.focus_chat.observation_context_size,
)
chat_talking_prompt = await build_readable_messages(
message_list_before_now,

View File

@@ -80,4 +80,4 @@ class ActionInfo(InfoBase):
Returns:
bool: 如果有任何动作需要添加或移除则返回True
"""
return bool(self.get_add_actions() or self.get_remove_actions())
return bool(self.get_add_actions() or self.get_remove_actions())

View File

@@ -1,14 +1,10 @@
from typing import List, Optional, Any
from src.chat.focus_chat.info.obs_info import ObsInfo
from src.chat.heart_flow.observation.observation import Observation
from src.chat.focus_chat.info.info_base import InfoBase
from src.chat.focus_chat.info.action_info import ActionInfo
from .base_processor import BaseProcessor
from src.common.logger_manager import get_logger
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.heart_flow.observation.hfcloop_observation import HFCloopObservation
from src.chat.focus_chat.info.cycle_info import CycleInfo
from datetime import datetime
from typing import Dict
from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
@@ -55,10 +51,7 @@ class ActionProcessor(BaseProcessor):
# 处理Observation对象
if observations:
for obs in observations:
if isinstance(obs, HFCloopObservation):
# 创建动作信息
action_info = ActionInfo()
action_changes = await self.analyze_loop_actions(obs)
@@ -75,7 +68,6 @@ class ActionProcessor(BaseProcessor):
return processed_infos
async def analyze_loop_actions(self, obs: HFCloopObservation) -> Dict[str, List[str]]:
"""分析最近的循环内容并决定动作的增减
@@ -87,29 +79,29 @@ class ActionProcessor(BaseProcessor):
}
"""
result = {"add": [], "remove": []}
# 获取最近10次循环
recent_cycles = obs.history_loop[-10:] if len(obs.history_loop) > 10 else obs.history_loop
if not recent_cycles:
return result
# 统计no_reply的数量
no_reply_count = 0
reply_sequence = [] # 记录最近的动作序列
for cycle in recent_cycles:
action_type = cycle.loop_plan_info["action_result"]["action_type"]
if action_type == "no_reply":
no_reply_count += 1
reply_sequence.append(action_type == "reply")
# 检查no_reply比例
if len(recent_cycles) >= 5 and (no_reply_count / len(recent_cycles)) >= 0.8:
result["add"].append("exit_focus_chat")
# 获取最近三次的reply状态
last_three = reply_sequence[-3:] if len(reply_sequence) >= 3 else reply_sequence
# 根据最近的reply情况决定是否移除reply动作
if len(last_three) >= 3 and all(last_three):
# 如果最近三次都是reply直接移除
@@ -122,5 +114,5 @@ class ActionProcessor(BaseProcessor):
# 如果最近一次是reply20%概率移除
if random.random() < 0.2:
result["remove"].append("reply")
return result

View File

@@ -5,7 +5,7 @@ from src.config.config import global_config
import time
import traceback
from src.common.logger_manager import get_logger
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.utils.json_utils import safe_json_dumps
from src.chat.message_receive.chat_stream import chat_manager
@@ -194,7 +194,6 @@ class MindProcessor(BaseProcessor):
# ---------- 3. 准备个性化数据 ----------
# 获取个性化信息
individuality = Individuality.get_instance()
relation_prompt = ""
for person in person_list:

View File

@@ -5,7 +5,7 @@ from src.config.config import global_config
import time
import traceback
from src.common.logger_manager import get_logger
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.message_receive.chat_stream import chat_manager
from src.chat.person_info.relationship_manager import relationship_manager
@@ -21,8 +21,9 @@ logger = get_logger("processor")
def init_prompt():
indentify_prompt = """
你的名字是{bot_name},你是一个AI但是你伪装成了一个人类你的人格是{prompt_personality}
的头像形象是一只橙色的鱼,头上有绿色的树叶
{name_block}
是一个AI但是你伪装成了一个人类你的人格是{prompt_personality}
{indentify_block}
{relation_prompt}
{memory_str}
@@ -125,16 +126,22 @@ class SelfProcessor(BaseProcessor):
# hfcloop_observe_info = observation.get_observe_info()
pass
individuality = Individuality.get_instance()
personality_block = individuality.get_prompt(x_person=2, level=2)
nickname_str = ""
for nicknames in global_config.bot.alias_names:
nickname_str += f"{nicknames},"
name_block = f"你的名字是{global_config.bot.nickname},你的昵称有{nickname_str},有人也会用这些昵称称呼你。"
personality_block = individuality.get_personality_prompt(x_person=2, level=2)
identity_block = individuality.get_identity_prompt(x_person=2, level=2)
relation_prompt = ""
for person in person_list:
relation_prompt += await relationship_manager.build_relationship_info(person, is_id=True)
prompt = (await global_prompt_manager.get_prompt_async("indentify_prompt")).format(
bot_name=individuality.name,
name_block=name_block,
prompt_personality=personality_block,
indentify_block=identity_block,
memory_str=memory_str,
relation_prompt=relation_prompt,
time_now=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),

View File

@@ -3,7 +3,7 @@ from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
import time
from src.common.logger_manager import get_logger
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.tools.tool_use import ToolUser
from src.chat.utils.json_utils import process_llm_tool_calls
@@ -133,7 +133,7 @@ class ToolProcessor(BaseProcessor):
relation_prompt += await relationship_manager.build_relationship_info(person, is_id=True)
# 获取个性信息
individuality = Individuality.get_instance()
# prompt_personality = individuality.get_prompt(x_person=2, level=2)
# 获取时间信息

View File

@@ -1,9 +1,8 @@
from typing import Dict, List, Optional, Callable, Coroutine, Type, Any
from typing import Dict, List, Optional, Type, Any
from src.chat.focus_chat.planners.actions.base_action import BaseAction, _ACTION_REGISTRY
from src.chat.heart_flow.observation.observation import Observation
from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.focus_chat.heartFC_Cycleinfo import CycleDetail
from src.common.logger_manager import get_logger
import importlib
import pkgutil

View File

@@ -1,12 +1,9 @@
import asyncio
import traceback
from src.common.logger_manager import get_logger
from src.chat.utils.timer_calculator import Timer
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action
from typing import Tuple, List, Callable, Coroutine
from typing import Tuple, List
from src.chat.heart_flow.observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.heart_flow.sub_heartflow import SubHeartFlow
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.heart_flow.heartflow import heartflow
from src.chat.heart_flow.sub_heartflow import ChatState
@@ -61,8 +58,6 @@ class ExitFocusChatAction(BaseAction):
self._shutting_down = shutting_down
self.chat_id = chat_stream.stream_id
async def handle_action(self) -> Tuple[bool, str]:
"""
处理退出专注聊天的情况
@@ -95,7 +90,6 @@ class ExitFocusChatAction(BaseAction):
logger.warning(f"{self.log_prefix} {warning_msg}")
return False, warning_msg
return True, status_message
except asyncio.CancelledError:
@@ -105,4 +99,4 @@ class ExitFocusChatAction(BaseAction):
error_msg = f"处理 'exit_focus_chat' 时发生错误: {str(e)}"
logger.error(f"{self.log_prefix} {error_msg}")
logger.error(traceback.format_exc())
return False, error_msg
return False, error_msg

View File

@@ -3,7 +3,7 @@ import traceback
from src.common.logger_manager import get_logger
from src.chat.utils.timer_calculator import Timer
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action
from typing import Tuple, List, Callable, Coroutine
from typing import Tuple, List
from src.chat.heart_flow.observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp

View File

@@ -12,7 +12,7 @@ from src.chat.focus_chat.info.action_info import ActionInfo
from src.chat.focus_chat.info.structured_info import StructuredInfo
from src.common.logger_manager import get_logger
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.chat.focus_chat.planners.action_manager import ActionManager
logger = get_logger("planner")
@@ -92,37 +92,37 @@ class ActionPlanner:
try:
# 获取观察信息
extra_info: list[str] = []
# 首先处理动作变更
for info in all_plan_info:
if isinstance(info, ActionInfo) and info.has_changes():
add_actions = info.get_add_actions()
remove_actions = info.get_remove_actions()
reason = info.get_reason()
# 处理动作的增加
for action_name in add_actions:
if action_name in self.action_manager.get_registered_actions():
self.action_manager.add_action_to_using(action_name)
logger.debug(f"{self.log_prefix}添加动作: {action_name}, 原因: {reason}")
# 处理动作的移除
for action_name in remove_actions:
self.action_manager.remove_action_from_using(action_name)
logger.debug(f"{self.log_prefix}移除动作: {action_name}, 原因: {reason}")
# 如果当前选择的动作被移除了更新为no_reply
if action in remove_actions:
action = "no_reply"
reasoning = f"之前选择的动作{action}已被移除,原因: {reason}"
# 继续处理其他信息
for info in all_plan_info:
if isinstance(info, ObsInfo):
observed_messages = info.get_talking_message()
observed_messages_str = info.get_talking_message_str_truncate()
chat_type = info.get_chat_type()
is_group_chat = (chat_type == "group")
is_group_chat = chat_type == "group"
elif isinstance(info, MindInfo):
current_mind = info.get_current_mind()
elif isinstance(info, CycleInfo):
@@ -134,20 +134,16 @@ class ActionPlanner:
# 获取当前可用的动作
current_available_actions = self.action_manager.get_using_actions()
# 如果没有可用动作直接返回no_reply
if not current_available_actions:
logger.warning(f"{self.log_prefix}没有可用的动作将使用no_reply")
action = "no_reply"
reasoning = "没有可用的动作"
return {
"action_result": {
"action_type": action,
"action_data": action_data,
"reasoning": reasoning
},
"action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning},
"current_mind": current_mind,
"observed_messages": observed_messages
"observed_messages": observed_messages,
}
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
@@ -271,7 +267,6 @@ class ActionPlanner:
else:
mind_info_block = "你刚参与聊天"
individuality = Individuality.get_instance()
personality_block = individuality.get_prompt(x_person=2, level=2)
action_options_block = ""

View File

@@ -4,7 +4,7 @@ from typing import Optional, Coroutine, Callable, Any, List
from src.common.logger_manager import get_logger
from src.chat.heart_flow.mai_state_manager import MaiStateManager, MaiStateInfo
from src.chat.heart_flow.subheartflow_manager import SubHeartflowManager
from src.config.config import global_config
logger = get_logger("background_tasks")
@@ -94,13 +94,6 @@ class BackgroundTaskManager:
f"清理任务已启动 间隔:{CLEANUP_INTERVAL_SECONDS}s",
"_cleanup_task",
),
# 新增兴趣评估任务配置
(
self._run_into_focus_cycle,
"debug", # 设为debug避免过多日志
f"专注评估任务已启动 间隔:{INTEREST_EVAL_INTERVAL_SECONDS}s",
"_into_focus_task",
),
# 新增私聊激活任务配置
(
# Use lambda to pass the interval to the runner function
@@ -111,6 +104,19 @@ class BackgroundTaskManager:
),
]
# 根据 chat_mode 条件添加专注评估任务
if not (global_config.chat.chat_mode == "normal"):
task_configs.append(
(
self._run_into_focus_cycle,
"debug", # 设为debug避免过多日志
f"专注评估任务已启动 间隔:{INTEREST_EVAL_INTERVAL_SECONDS}s",
"_into_focus_task",
)
)
else:
logger.info("聊天模式为 normal跳过启动专注评估任务")
# 统一启动所有任务
for task_func, log_level, log_msg, task_attr_name in task_configs:
# 检查任务变量是否存在且未完成
@@ -183,7 +189,6 @@ class BackgroundTaskManager:
logger.info("检测到离线,停用所有子心流")
await self.subheartflow_manager.deactivate_all_subflows()
async def _perform_cleanup_work(self):
"""执行子心流清理任务
1. 获取需要清理的不活跃子心流列表
@@ -209,18 +214,15 @@ class BackgroundTaskManager:
# 记录最终清理结果
logger.info(f"[清理任务] 清理完成, 共停止 {stopped_count}/{len(flows_to_stop)} 个子心流")
# --- 新增兴趣评估工作函数 ---
async def _perform_into_focus_work(self):
"""执行一轮子心流兴趣评估与提升检查。"""
# 直接调用 subheartflow_manager 的方法,并传递当前状态信息
await self.subheartflow_manager.sbhf_absent_into_focus()
await self.subheartflow_manager.sbhf_normal_into_focus()
async def _run_state_update_cycle(self, interval: int):
await _run_periodic_loop(task_name="State Update", interval=interval, task_func=self._perform_state_update_work)
async def _run_cleanup_cycle(self):
await _run_periodic_loop(
task_name="Subflow Cleanup", interval=CLEANUP_INTERVAL_SECONDS, task_func=self._perform_cleanup_work

View File

@@ -4,13 +4,13 @@ import enum
class ChatState(enum.Enum):
ABSENT = "没在看群"
CHAT = "随便水群"
NORMAL = "随便水群"
FOCUSED = "认真水群"
class ChatStateInfo:
def __init__(self):
self.chat_status: ChatState = ChatState.CHAT
self.chat_status: ChatState = ChatState.NORMAL
self.current_state_time = 120
self.mood_manager = mood_manager

View File

@@ -1,9 +1,6 @@
from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState
from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
from src.common.logger_manager import get_logger
from typing import Any, Optional
from src.tools.tool_use import ToolUser
from src.chat.heart_flow.mai_state_manager import MaiStateInfo, MaiStateManager
from src.chat.heart_flow.subheartflow_manager import SubHeartflowManager
from src.chat.heart_flow.background_tasks import BackgroundTaskManager # Import BackgroundTaskManager

View File

@@ -4,21 +4,10 @@ import random
from typing import List, Tuple, Optional
from src.common.logger_manager import get_logger
from src.manager.mood_manager import mood_manager
from src.config.config import global_config
logger = get_logger("mai_state")
# -- 状态相关的可配置参数 (可以从 glocal_config 加载) --
# The line `enable_unlimited_hfc_chat = False` is setting a configuration parameter that controls
# whether a specific debugging feature is enabled or not. When `enable_unlimited_hfc_chat` is set to
# `False`, it means that the debugging feature for unlimited focused chatting is disabled.
# enable_unlimited_hfc_chat = True # 调试用:无限专注聊天
enable_unlimited_hfc_chat = False
prevent_offline_state = True
# 目前默认不启用OFFLINE状
class MaiState(enum.Enum):
"""
聊天状态:
@@ -141,10 +130,6 @@ class MaiStateManager:
)
next_state = resolved_candidate
if enable_unlimited_hfc_chat:
logger.debug("调试用:开挂了,强制切换到专注聊天")
next_state = MaiState.FOCUSED_CHAT
if next_state is not None and next_state != current_status:
return next_state
else:

View File

@@ -57,7 +57,7 @@ class ChattingObservation(Observation):
self.talking_message_str_truncate = ""
self.name = global_config.bot.nickname
self.nick_name = global_config.bot.alias_names
self.max_now_obs_len = global_config.chat.observation_context_size
self.max_now_obs_len = global_config.focus_chat.observation_context_size
self.overlap_len = global_config.focus_chat.compressed_length
self.mid_memories = []
self.max_mid_memory_len = global_config.focus_chat.compress_length_limit

View File

@@ -18,7 +18,7 @@ class HFCloopObservation:
self.last_observe_time = datetime.now().timestamp() # 初始化为当前时间
self.history_loop: List[CycleDetail] = []
self.action_manager: ActionManager = None
self.all_actions = {}
def get_observe_info(self):

View File

@@ -2,7 +2,7 @@ from .observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
import asyncio
import time
from typing import Optional, List, Dict, Tuple, Callable, Coroutine
from typing import Optional, List, Dict, Tuple
import traceback
from src.common.logger_manager import get_logger
from src.chat.message_receive.message import MessageRecv
@@ -23,7 +23,6 @@ class SubHeartflow:
self,
subheartflow_id,
mai_states: MaiStateInfo,
hfc_no_reply_callback: Callable[[], Coroutine[None, None, None]],
):
"""子心流初始化函数
@@ -35,7 +34,6 @@ class SubHeartflow:
# 基础属性,两个值是一样的
self.subheartflow_id = subheartflow_id
self.chat_id = subheartflow_id
self.hfc_no_reply_callback = hfc_no_reply_callback
# 麦麦的状态
self.mai_states = mai_states
@@ -92,7 +90,7 @@ class SubHeartflow:
# 创建并初始化 normal_chat_instance
chat_stream = chat_manager.get_stream(self.chat_id)
if chat_stream:
self.normal_chat_instance = NormalChat(chat_stream=chat_stream,interest_dict=self.get_interest_dict())
self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict())
await self.normal_chat_instance.initialize()
await self.normal_chat_instance.start_chat()
logger.info(f"{self.log_prefix} NormalChat 实例已创建并启动。")
@@ -189,7 +187,7 @@ class SubHeartflow:
# 创建 HeartFChatting 实例,并传递 从构造函数传入的 回调函数
self.heart_fc_instance = HeartFChatting(
chat_id=self.subheartflow_id,
observations=self.observations,
observations=self.observations,
)
# 初始化并启动 HeartFChatting
@@ -216,7 +214,7 @@ class SubHeartflow:
state_changed = False
log_prefix = f"[{self.log_prefix}]"
if new_state == ChatState.CHAT:
if new_state == ChatState.NORMAL:
logger.debug(f"{log_prefix} 准备进入或保持 普通聊天 状态")
if await self._start_normal_chat():
logger.debug(f"{log_prefix} 成功进入或保持 NormalChat 状态。")

View File

@@ -2,13 +2,11 @@ import asyncio
import time
import random
from typing import Dict, Any, Optional, List
import functools
from src.common.logger_manager import get_logger
from src.chat.message_receive.chat_stream import chat_manager
from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState
from src.chat.heart_flow.mai_state_manager import MaiStateInfo
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.config.config import global_config
# 初始化日志记录器
@@ -62,7 +60,6 @@ class SubHeartflowManager:
self._lock = asyncio.Lock() # 用于保护 self.subheartflows 的访问
self.mai_state_info: MaiStateInfo = mai_state_info # 存储传入的 MaiStateInfo 实例
async def force_change_state(self, subflow_id: Any, target_state: ChatState) -> bool:
"""强制改变指定子心流的状态"""
async with self._lock:
@@ -101,16 +98,10 @@ class SubHeartflowManager:
return subflow
try:
# --- 使用 functools.partial 创建 HFC 回调 --- #
# 将 manager 的 _handle_hfc_no_reply 方法与当前的 subheartflow_id 绑定
hfc_callback = functools.partial(self._handle_hfc_no_reply, subheartflow_id)
# --- 结束创建回调 --- #
# 初始化子心流, 传入 mai_state_info 和 partial 创建的回调
# 初始化子心流, 传入 mai_state_info
new_subflow = SubHeartflow(
subheartflow_id,
self.mai_state_info,
hfc_callback, # <-- 传递 partial 创建的回调
)
# 异步初始化
@@ -199,22 +190,14 @@ class SubHeartflowManager:
f"{log_prefix} 完成,共处理 {processed_count} 个子心流,成功将 {changed_count} 个非 ABSENT 子心流的状态更改为 ABSENT。"
)
async def sbhf_absent_into_focus(self):
async def sbhf_normal_into_focus(self):
"""评估子心流兴趣度满足条件则提升到FOCUSED状态基于start_hfc_probability"""
try:
current_state = self.mai_state_info.get_current_state()
# 检查是否允许进入 FOCUS 模式
if not global_config.chat.allow_focus_mode:
if int(time.time()) % 60 == 0: # 每60秒输出一次日志避免刷屏
logger.trace("未开启 FOCUSED 状态 (allow_focus_mode=False)")
return
for sub_hf in list(self.subheartflows.values()):
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
# 跳过非CHAT状态或已经是FOCUSED状态的子心流
# 跳过已经是FOCUSED状态的子心流
if sub_hf.chat_state.chat_status == ChatState.FOCUSED:
continue
@@ -225,13 +208,6 @@ class SubHeartflowManager:
f"{stream_name},现在状态: {sub_hf.chat_state.chat_status.value},进入专注概率: {sub_hf.interest_chatting.start_hfc_probability}"
)
# 调试用
from .mai_state_manager import enable_unlimited_hfc_chat
if not enable_unlimited_hfc_chat:
if sub_hf.chat_state.chat_status != ChatState.CHAT:
continue
if random.random() >= sub_hf.interest_chatting.start_hfc_probability:
continue
@@ -250,12 +226,11 @@ class SubHeartflowManager:
except Exception as e:
logger.error(f"启动HFC 兴趣评估失败: {e}", exc_info=True)
async def sbhf_focus_into_absent_or_chat(self, subflow_id: Any):
async def sbhf_focus_into_normal(self, subflow_id: Any):
"""
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 CHAT
接收来自 HeartFChatting 的请求,将特定子心流的状态转换为 NORMAL
通常在连续多次 "no_reply" 后被调用。
对于私聊和群聊,都转换为 CHAT
对于私聊和群聊,都转换为 NORMAL
Args:
subflow_id: 需要转换状态的子心流 ID。
@@ -263,15 +238,15 @@ class SubHeartflowManager:
async with self._lock:
subflow = self.subheartflows.get(subflow_id)
if not subflow:
logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id}CHAT")
logger.warning(f"[状态转换请求] 尝试转换不存在的子心流 {subflow_id}NORMAL")
return
stream_name = chat_manager.get_stream_name(subflow_id) or subflow_id
current_state = subflow.chat_state.chat_status
if current_state == ChatState.FOCUSED:
target_state = ChatState.CHAT
log_reason = "转为CHAT"
target_state = ChatState.NORMAL
log_reason = "转为NORMAL"
logger.info(
f"[状态转换请求] 接收到请求,将 {stream_name} (当前: {current_state.value}) 尝试转换为 {target_state.value} ({log_reason})"
@@ -292,34 +267,10 @@ class SubHeartflowManager:
f"[状态转换请求] 转换 {stream_name}{target_state.value} 时出错: {e}", exc_info=True
)
elif current_state == ChatState.ABSENT:
logger.debug(f"[状态转换请求] {stream_name} 处于 ABSENT 状态,尝试转为 CHAT")
await subflow.change_chat_state(ChatState.CHAT)
logger.debug(f"[状态转换请求] {stream_name} 处于 ABSENT 状态,尝试转为 NORMAL")
await subflow.change_chat_state(ChatState.NORMAL)
else:
logger.debug(
f"[状态转换请求] {stream_name} 当前状态为 {current_state.value},无需转换"
)
def count_subflows_by_state(self, state: ChatState) -> int:
"""统计指定状态的子心流数量"""
count = 0
# 遍历所有子心流实例
for subheartflow in self.subheartflows.values():
# 检查子心流状态是否匹配
if subheartflow.chat_state.chat_status == state:
count += 1
return count
def count_subflows_by_state_nolock(self, state: ChatState) -> int:
"""
统计指定状态的子心流数量 (不上锁版本)。
警告:仅应在已持有 self._lock 的上下文中使用此方法。
"""
count = 0
for subheartflow in self.subheartflows.values():
if subheartflow.chat_state.chat_status == state:
count += 1
return count
logger.debug(f"[状态转换请求] {stream_name} 当前状态为 {current_state.value},无需转换")
async def delete_subflow(self, subheartflow_id: Any):
"""删除指定的子心流。"""
@@ -336,28 +287,14 @@ class SubHeartflowManager:
else:
logger.warning(f"尝试删除不存在的 SubHeartflow: {subheartflow_id}")
async def _handle_hfc_no_reply(self, subheartflow_id: Any):
"""处理来自 HeartFChatting 的连续无回复信号 (通过 partial 绑定 ID)"""
# 注意:这里不需要再获取锁,因为 sbhf_focus_into_absent_or_chat 内部会处理锁
logger.debug(f"[管理器 HFC 处理器] 接收到来自 {subheartflow_id} 的 HFC 无回复信号")
await self.sbhf_focus_into_absent_or_chat(subheartflow_id)
# --- 新增:处理私聊从 ABSENT 直接到 FOCUSED 的逻辑 --- #
async def sbhf_absent_private_into_focus(self):
"""检查 ABSENT 状态的私聊子心流是否有新活动,若有且未达 FOCUSED 上限,则直接转换为 FOCUSED。"""
"""检查 ABSENT 状态的私聊子心流是否有新活动,若有则直接转换为 FOCUSED。"""
log_prefix_task = "[私聊激活检查]"
transitioned_count = 0
checked_count = 0
# --- 检查是否允许 FOCUS 模式 --- #
if not global_config.chat.allow_focus_mode:
return
async with self._lock:
# --- 获取当前 FOCUSED 计数 (不上锁版本) --- #
current_focused_count = self.count_subflows_by_state_nolock(ChatState.FOCUSED)
# --- 筛选出所有 ABSENT 状态的私聊子心流 --- #
eligible_subflows = [
hf
@@ -372,7 +309,6 @@ class SubHeartflowManager:
# --- 遍历评估每个符合条件的私聊 --- #
for sub_hf in eligible_subflows:
flow_id = sub_hf.subheartflow_id
stream_name = chat_manager.get_stream_name(flow_id) or flow_id
log_prefix = f"[{stream_name}]({log_prefix_task})"
@@ -393,13 +329,12 @@ class SubHeartflowManager:
else:
logger.warning(f"{log_prefix} 无法获取主要观察者来检查活动状态。")
# --- 如果活跃且未达上限,则尝试转换 --- #
# --- 如果活跃,则尝试转换 --- #
if is_active:
await sub_hf.change_chat_state(ChatState.FOCUSED)
# 确认转换成功
if sub_hf.chat_state.chat_status == ChatState.FOCUSED:
transitioned_count += 1
current_focused_count += 1 # 更新计数器以供本轮后续检查
logger.info(f"{log_prefix} 成功进入 FOCUSED 状态。")
else:
logger.warning(

View File

@@ -11,7 +11,6 @@ import jieba
import networkx as nx
import numpy as np
from collections import Counter
from ...common.database.database import memory_db as db
from ...chat.models.utils_model import LLMRequest
from src.common.logger_manager import get_logger
from src.chat.memory_system.sample_distribution import MemoryBuildScheduler # 分布生成器

View File

@@ -7,7 +7,7 @@ from src.chat.message_receive.chat_stream import chat_manager
from src.chat.message_receive.message import MessageRecv
from src.experimental.only_message_process import MessageProcessor
from src.experimental.PFC.pfc_manager import PFCManager
from src.chat.focus_chat.heartflow_processor import HeartFCProcessor
from src.chat.focus_chat.heartflow_message_revceiver import HeartFCMessageReceiver
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.config.config import global_config
@@ -23,7 +23,7 @@ class ChatBot:
self.bot = None # bot 实例引用
self._started = False
self.mood_manager = mood_manager # 获取情绪管理器单例
self.heartflow_processor = HeartFCProcessor() # 新增
self.heartflow_message_receiver = HeartFCMessageReceiver() # 新增
# 创建初始化PFC管理器的任务会在_ensure_started时执行
self.only_process_chat = MessageProcessor()
@@ -110,11 +110,11 @@ class ChatBot:
# 禁止PFC进入普通的心流消息处理逻辑
else:
logger.trace("进入普通心流私聊处理")
await self.heartflow_processor.process_message(message_data)
await self.heartflow_message_receiver.process_message(message_data)
# 群聊默认进入心流消息处理逻辑
else:
logger.trace(f"检测到群聊消息群ID: {group_info.group_id}")
await self.heartflow_processor.process_message(message_data)
await self.heartflow_message_receiver.process_message(message_data)
if template_group_name:
async with global_prompt_manager.async_message_scope(template_group_name):

View File

@@ -26,7 +26,7 @@ logger = get_logger("normal_chat")
class NormalChat:
def __init__(self, chat_stream: ChatStream, interest_dict: dict = {}):
def __init__(self, chat_stream: ChatStream, interest_dict: dict = None):
"""初始化 NormalChat 实例。只进行同步操作。"""
# Basic info from chat_stream (sync)
@@ -36,7 +36,7 @@ class NormalChat:
self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id
# Interest dict
self.interest_dict = interest_dict
self.interest_dict = interest_dict or {}
# --- Initialize attributes (defaults) ---
self.is_group_chat: bool = False
@@ -200,7 +200,6 @@ class NormalChat:
logger.info(f"[{self.stream_name}] 兴趣监控任务被取消或置空,退出")
break
items_to_process = list(self.interest_dict.items())
if not items_to_process:
continue

View File

@@ -49,7 +49,7 @@ class ClassicalWillingManager(BaseWillingManager):
# 检查群组权限(如果是群聊)
if (
willing_info.group_info
and willing_info.group_info.group_id in global_config.chat_target.talk_frequency_down_groups
and willing_info.group_info.group_id in global_config.normal_chat.talk_frequency_down_groups
):
reply_probability = reply_probability / global_config.normal_chat.down_frequency_rate

View File

@@ -180,7 +180,7 @@ class MxpWillingManager(BaseWillingManager):
if w_info.is_emoji:
probability *= global_config.normal_chat.emoji_response_penalty
if w_info.group_info and w_info.group_info.group_id in global_config.chat_target.talk_frequency_down_groups:
if w_info.group_info and w_info.group_info.group_id in global_config.normal_chat.talk_frequency_down_groups:
probability /= global_config.normal_chat.down_frequency_rate
self.temporary_willing = current_willing

View File

@@ -9,7 +9,7 @@ import asyncio
import numpy as np
from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
import matplotlib
@@ -257,7 +257,6 @@ class PersonInfoManager:
current_name_set = set(self.person_name_list.values())
while current_try < max_retries:
individuality = Individuality.get_instance()
prompt_personality = individuality.get_prompt(x_person=2, level=1)
bot_name = individuality.personality.bot_nickname

View File

@@ -127,7 +127,7 @@ class InfoCatcher:
Messages.select()
.where((Messages.chat_id == chat_id_val) & (Messages.message_id < message_id_val))
.order_by(Messages.time.desc())
.limit(global_config.chat.observation_context_size * 3)
.limit(global_config.focus_chat.observation_context_size * 3)
)
return list(messages_before_query)

View File

@@ -6,16 +6,13 @@ from collections import Counter
import jieba
import numpy as np
from maim_message import UserInfo
from pymongo.errors import PyMongoError
from src.common.logger import get_module_logger
from src.manager.mood_manager import mood_manager
from ..message_receive.message import MessageRecv
from ..models.utils_model import LLMRequest
from .typo_generator import ChineseTypoGenerator
from ...common.database.database import db
from ...config.config import global_config
from ...common.database.database_model import Messages
from ...common.message_repository import find_messages, count_messages
logger = get_module_logger("chat_utils")
@@ -112,11 +109,7 @@ async def get_embedding(text, request_type="embedding"):
def get_recent_group_detailed_plain_text(chat_stream_id: str, limit: int = 12, combine=False):
filter_query = {"chat_id": chat_stream_id}
sort_order = [("time", -1)]
recent_messages = find_messages(
message_filter=filter_query,
sort=sort_order,
limit=limit
)
recent_messages = find_messages(message_filter=filter_query, sort=sort_order, limit=limit)
if not recent_messages:
return []
@@ -141,23 +134,21 @@ def get_recent_group_speaker(chat_stream_id: str, sender, limit: int = 12) -> li
# 获取当前群聊记录内发言的人
filter_query = {"chat_id": chat_stream_id}
sort_order = [("time", -1)]
recent_messages = find_messages(
message_filter=filter_query,
sort=sort_order,
limit=limit
)
recent_messages = find_messages(message_filter=filter_query, sort=sort_order, limit=limit)
if not recent_messages:
return []
who_chat_in_group = []
for msg_db_data in recent_messages:
user_info = UserInfo.from_dict({
"platform": msg_db_data["user_platform"],
"user_id": msg_db_data["user_id"],
"user_nickname": msg_db_data["user_nickname"],
"user_cardname": msg_db_data.get("user_cardname", "")
})
user_info = UserInfo.from_dict(
{
"platform": msg_db_data["user_platform"],
"user_id": msg_db_data["user_id"],
"user_nickname": msg_db_data["user_nickname"],
"user_cardname": msg_db_data.get("user_cardname", ""),
}
)
if (
(user_info.platform, user_info.user_id) != sender
and user_info.user_id != global_config.bot.qq_account
@@ -579,14 +570,13 @@ def count_messages_between(start_time: float, end_time: float, stream_id: str) -
# 使用message_repository中的count_messages和find_messages函数
# 构建查询条件
filter_query = {"chat_id": stream_id, "time": {"$gt": start_time, "$lte": end_time}}
try:
# 先获取消息数量
count = count_messages(filter_query)
# 获取消息内容计算总长度
messages = find_messages(message_filter=filter_query)
total_length = sum(len(msg.get("processed_plain_text", "")) for msg in messages)

View File

@@ -71,9 +71,9 @@ class TelemetryHeartBeatTask(AsyncTask):
)
logger.debug(f"{TELEMETRY_SERVER_URL}/stat/reg_client")
logger.debug(local_storage["deploy_time"])
logger.debug(response)
if response.status_code == 200:
@@ -111,7 +111,7 @@ class TelemetryHeartBeatTask(AsyncTask):
}
logger.debug(f"正在发送心跳到服务器: {self.server_url}")
logger.debug(headers)
response = requests.post(
@@ -119,7 +119,7 @@ class TelemetryHeartBeatTask(AsyncTask):
headers=headers,
json=self.info_dict,
)
logger.debug(response)
# 处理响应

View File

@@ -14,10 +14,9 @@ from rich.traceback import install
from src.config.config_base import ConfigBase
from src.config.official_configs import (
BotConfig,
ChatTargetConfig,
PersonalityConfig,
IdentityConfig,
PlatformsConfig,
ExpressionConfig,
ChatConfig,
NormalChatConfig,
FocusChatConfig,
@@ -30,6 +29,7 @@ from src.config.official_configs import (
TelemetryConfig,
ExperimentalConfig,
ModelConfig,
MessageReceiveConfig,
)
install(extra_lines=3)
@@ -139,14 +139,14 @@ class Config(ConfigBase):
MMC_VERSION: str = field(default=MMC_VERSION, repr=False, init=False) # 硬编码的版本信息
bot: BotConfig
chat_target: ChatTargetConfig
personality: PersonalityConfig
identity: IdentityConfig
platforms: PlatformsConfig
chat: ChatConfig
message_receive: MessageReceiveConfig
normal_chat: NormalChatConfig
focus_chat: FocusChatConfig
emoji: EmojiConfig
expression: ExpressionConfig
memory: MemoryConfig
mood: MoodConfig
keyword_reaction: KeywordReactionConfig

View File

@@ -26,23 +26,6 @@ class BotConfig(ConfigBase):
"""别名列表"""
@dataclass
class ChatTargetConfig(ConfigBase):
"""
聊天目标配置类
此类中有聊天的群组和用户配置
"""
talk_allowed_groups: set[str] = field(default_factory=lambda: set())
"""允许聊天的群组列表"""
talk_frequency_down_groups: set[str] = field(default_factory=lambda: set())
"""降低聊天频率的群组列表"""
ban_user_id: set[str] = field(default_factory=lambda: set())
"""禁止聊天的用户列表"""
@dataclass
class PersonalityConfig(ConfigBase):
"""人格配置类"""
@@ -50,9 +33,6 @@ class PersonalityConfig(ConfigBase):
personality_core: str
"""核心人格"""
expression_style: str
"""表达风格"""
personality_sides: list[str] = field(default_factory=lambda: [])
"""人格侧写"""
@@ -80,32 +60,17 @@ class IdentityConfig(ConfigBase):
"""身份特征"""
@dataclass
class PlatformsConfig(ConfigBase):
"""平台配置类"""
qq: str
"""QQ适配器连接URL配置"""
@dataclass
class ChatConfig(ConfigBase):
"""聊天配置类"""
allow_focus_mode: bool = True
"""是否允许专注聊天状态"""
chat_mode: str = "normal"
"""聊天模式"""
base_normal_chat_num: int = 3
"""最多允许多少个群进行普通聊天"""
base_focused_chat_num: int = 2
"""最多允许多少个群进行专注聊天"""
observation_context_size: int = 12
"""可观察到的最长上下文大小,超过这个值的上下文会被压缩"""
message_buffer: bool = True
"""消息缓冲器"""
@dataclass
class MessageReceiveConfig(ConfigBase):
"""消息接收配置类"""
ban_words: set[str] = field(default_factory=lambda: set())
"""过滤词列表"""
@@ -124,6 +89,12 @@ class NormalChatConfig(ConfigBase):
选择普通模型的概率为 1 - reasoning_normal_model_probability
"""
max_context_size: int = 15
"""上下文长度"""
message_buffer: bool = True
"""消息缓冲器"""
emoji_chance: float = 0.2
"""发送表情包的基础概率"""
@@ -139,6 +110,9 @@ class NormalChatConfig(ConfigBase):
response_interested_rate_amplifier: float = 1.0
"""回复兴趣度放大系数"""
talk_frequency_down_groups: list[str] = field(default_factory=lambda: [])
"""降低回复频率的群组"""
down_frequency_rate: float = 3.0
"""降低回复频率的群组回复意愿降低系数"""
@@ -162,6 +136,9 @@ class FocusChatConfig(ConfigBase):
default_decay_rate_per_second: float = 0.98
"""默认衰减率,越大衰减越快"""
observation_context_size: int = 12
"""可观察到的最长上下文大小,超过这个值的上下文会被压缩"""
consecutive_no_reply_threshold: int = 3
"""连续不回复的次数阈值"""
@@ -172,6 +149,20 @@ class FocusChatConfig(ConfigBase):
"""最多压缩份数,超过该数值的压缩上下文会被删除"""
@dataclass
class ExpressionConfig(ConfigBase):
"""表达配置类"""
expression_style: str = ""
"""表达风格"""
learning_interval: int = 300
"""学习间隔(秒)"""
enable_expression_learning: bool = True
"""是否启用表达学习"""
@dataclass
class EmojiConfig(ConfigBase):
"""表情包配置类"""
@@ -340,11 +331,8 @@ class TelemetryConfig(ConfigBase):
class ExperimentalConfig(ConfigBase):
"""实验功能配置类"""
# enable_friend_chat: bool = False
# """是否启用好友聊天"""
# talk_allowed_private: set[str] = field(default_factory=lambda: set())
# """允许聊天的私聊列表"""
enable_friend_chat: bool = False
"""是否启用好友聊天"""
pfc_chatting: bool = False
"""是否启用PFC"""

View File

@@ -5,7 +5,7 @@ from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
from src.experimental.PFC.chat_observer import ChatObserver
from src.experimental.PFC.pfc_utils import get_items_from_json
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.experimental.PFC.observation_info import ObservationInfo
from src.experimental.PFC.conversation_info import ConversationInfo
from src.chat.utils.chat_message_builder import build_readable_messages
@@ -113,7 +113,7 @@ class ActionPlanner:
max_tokens=1500,
request_type="action_planning",
)
self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3)
self.personality_info = individuality.get_prompt(x_person=2, level=3)
self.name = global_config.bot.nickname
self.private_name = private_name
self.chat_observer = ChatObserver.get_instance(stream_id, private_name)

View File

@@ -4,7 +4,7 @@ from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
from src.experimental.PFC.chat_observer import ChatObserver
from src.experimental.PFC.pfc_utils import get_items_from_json
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from src.experimental.PFC.conversation_info import ConversationInfo
from src.experimental.PFC.observation_info import ObservationInfo
from src.chat.utils.chat_message_builder import build_readable_messages
@@ -47,7 +47,7 @@ class GoalAnalyzer:
model=global_config.model.normal, temperature=0.7, max_tokens=1000, request_type="conversation_goal"
)
self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3)
self.personality_info = individuality.get_prompt(x_person=2, level=3)
self.name = global_config.bot.nickname
self.nick_name = global_config.bot.alias_names
self.private_name = private_name

View File

@@ -4,7 +4,7 @@ from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
from src.experimental.PFC.chat_observer import ChatObserver
from src.experimental.PFC.reply_checker import ReplyChecker
from src.individuality.individuality import Individuality
from src.individuality.individuality import individuality
from .observation_info import ObservationInfo
from .conversation_info import ConversationInfo
from src.chat.utils.chat_message_builder import build_readable_messages
@@ -92,7 +92,7 @@ class ReplyGenerator:
max_tokens=300,
request_type="reply_generation",
)
self.personality_info = Individuality.get_instance().get_prompt(x_person=2, level=3)
self.personality_info = individuality.get_prompt(x_person=2, level=3)
self.name = global_config.bot.nickname
self.private_name = private_name
self.chat_observer = ChatObserver.get_instance(stream_id, private_name)

View File

@@ -2,7 +2,7 @@ from src.common.logger import get_module_logger
from .chat_observer import ChatObserver
from .conversation_info import ConversationInfo
# from src.individuality.individuality import Individuality # 不再需要
# from src.individuality.individuality import individuality,Individuality # 不再需要
from src.config.config import global_config
import time
import asyncio

View File

@@ -0,0 +1,159 @@
import random
from src.common.logger_manager import get_logger
from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from typing import List, Tuple
import os
import json
logger = get_logger("expressor")
def init_prompt() -> None:
personality_expression_prompt = """
{personality}
请从以上人设中总结出这个角色可能的语言风格
思考回复的特殊内容和情感
思考有没有特殊的梗,一并总结成语言风格
总结成如下格式的规律,总结的内容要详细,但具有概括性:
"xxx"时,可以"xxx", xxx不超过10个字
例如:
"表示十分惊叹"时,使用"我嘞个xxxx"
"表示讽刺的赞同,不想讲道理"时,使用"对对对"
"想说明某个观点,但懒得明说",使用"懂的都懂"
现在请你概括
"""
Prompt(personality_expression_prompt, "personality_expression_prompt")
class PersonalityExpression:
def __init__(self):
self.express_learn_model: LLMRequest = LLMRequest(
model=global_config.model.normal,
temperature=0.1,
max_tokens=256,
request_type="response_heartflow",
)
self.meta_file_path = os.path.join("data", "expression", "personality", "expression_style_meta.json")
self.expressions_file_path = os.path.join("data", "expression", "personality", "expressions.json")
self.max_calculations = 5
def _read_meta_data(self):
if os.path.exists(self.meta_file_path):
try:
with open(self.meta_file_path, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError:
logger.warning(f"无法解析 {self.meta_file_path} 中的JSON数据将重新开始。")
return {"last_style_text": None, "count": 0}
return {"last_style_text": None, "count": 0}
def _write_meta_data(self, data):
os.makedirs(os.path.dirname(self.meta_file_path), exist_ok=True)
with open(self.meta_file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
async def extract_and_store_personality_expressions(self):
"""
检查data/expression/personality目录不存在则创建。
用peronality变量作为chat_str调用LLM生成表达风格解析后count=100存储到expressions.json。
如果expression_style发生变化则删除旧的expressions.json并重置计数。
对于相同的expression_style最多计算self.max_calculations次。
"""
os.makedirs(os.path.dirname(self.expressions_file_path), exist_ok=True)
current_style_text = global_config.expression.expression_style
meta_data = self._read_meta_data()
last_style_text = meta_data.get("last_style_text")
count = meta_data.get("count", 0)
if current_style_text != last_style_text:
logger.info(f"表达风格已从 '{last_style_text}' 变为 '{current_style_text}'。重置计数。")
count = 0
if os.path.exists(self.expressions_file_path):
try:
os.remove(self.expressions_file_path)
logger.info(f"已删除旧的表达文件: {self.expressions_file_path}")
except OSError as e:
logger.error(f"删除旧的表达文件 {self.expressions_file_path} 失败: {e}")
if count >= self.max_calculations:
logger.info(f"对于风格 '{current_style_text}' 已达到最大计算次数 ({self.max_calculations})。跳过提取。")
# 即使跳过,也更新元数据以反映当前风格已被识别且计数已满
self._write_meta_data({"last_style_text": current_style_text, "count": count})
return
# 构建prompt
prompt = await global_prompt_manager.format_prompt(
"personality_expression_prompt",
personality=current_style_text,
)
# logger.info(f"个性表达方式提取prompt: {prompt}")
try:
response, _ = await self.express_learn_model.generate_response_async(prompt)
except Exception as e:
logger.error(f"个性表达方式提取失败: {e}")
# 如果提取失败,保存当前的风格和未增加的计数
self._write_meta_data({"last_style_text": current_style_text, "count": count})
return
logger.info(f"个性表达方式提取response: {response}")
# chat_id用personality
expressions = self.parse_expression_response(response, "personality")
# 转为dict并count=100
result = []
for _, situation, style in expressions:
result.append({"situation": situation, "style": style, "count": 100})
# 超过50条时随机删除多余的只保留50条
if len(result) > 50:
remove_count = len(result) - 50
remove_indices = set(random.sample(range(len(result)), remove_count))
result = [item for idx, item in enumerate(result) if idx not in remove_indices]
with open(self.expressions_file_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
logger.info(f"已写入{len(result)}条表达到{self.expressions_file_path}")
# 成功提取后更新元数据
count += 1
self._write_meta_data({"last_style_text": current_style_text, "count": count})
logger.info(f"成功处理。风格 '{current_style_text}' 的计数现在是 {count}")
def parse_expression_response(self, response: str, chat_id: str) -> List[Tuple[str, str, str]]:
"""
解析LLM返回的表达风格总结每一行提取"""使用"之间的内容,存储为(situation, style)元组
"""
expressions: List[Tuple[str, str, str]] = []
for line in response.splitlines():
line = line.strip()
if not line:
continue
# 查找"当"和下一个引号
idx_when = line.find('"')
if idx_when == -1:
continue
idx_quote1 = idx_when + 1
idx_quote2 = line.find('"', idx_quote1 + 1)
if idx_quote2 == -1:
continue
situation = line[idx_quote1 + 1 : idx_quote2]
# 查找"使用"
idx_use = line.find('使用"', idx_quote2)
if idx_use == -1:
continue
idx_quote3 = idx_use + 2
idx_quote4 = line.find('"', idx_quote3 + 1)
if idx_quote4 == -1:
continue
style = line[idx_quote3 + 1 : idx_quote4]
expressions.append((chat_id, situation, style))
return expressions
init_prompt()

View File

@@ -3,6 +3,7 @@ from typing import Optional
from numpy import double
from .personality import Personality
from .identity import Identity
from .expression_style import PersonalityExpression
import random
from rich.traceback import install
@@ -12,36 +13,15 @@ install(extra_lines=3)
class Individuality:
"""个体特征管理类"""
_instance = None
def __init__(self):
if Individuality._instance is not None:
raise RuntimeError("Individuality 类是单例,请使用 get_instance() 方法获取实例。")
# 正常初始化实例属性
self.personality: Optional[Personality] = None
self.identity: Optional[Identity] = None
self.express_style: PersonalityExpression = PersonalityExpression()
self.name = ""
@classmethod
def get_instance(cls) -> "Individuality":
"""获取Individuality单例实例
Returns:
Individuality: 单例实例
"""
if cls._instance is None:
# 实例不存在,调用 cls() 创建新实例
# cls() 会调用 __init__
# 因为此时 cls._instance 仍然是 None__init__ 会正常执行初始化
new_instance = cls()
# 将新创建的实例赋值给类变量 _instance
cls._instance = new_instance
# 返回(新创建的或已存在的)单例实例
return cls._instance
def initialize(
async def initialize(
self,
bot_nickname: str,
personality_core: str,
@@ -76,6 +56,8 @@ class Individuality:
identity_detail=identity_detail, height=height, weight=weight, age=age, gender=gender, appearance=appearance
)
await self.express_style.extract_and_store_personality_expressions()
self.name = bot_nickname
def to_dict(self) -> dict:
@@ -88,7 +70,7 @@ class Individuality:
@classmethod
def from_dict(cls, data: dict) -> "Individuality":
"""从字典创建个体特征实例"""
instance = cls.get_instance()
instance = cls()
if data.get("personality"):
instance.personality = Personality.from_dict(data["personality"])
if data.get("identity"):
@@ -176,6 +158,10 @@ class Individuality:
identity_parts.append(f"年龄大约{self.identity.age}")
if self.identity.gender:
identity_parts.append(f"性别是{self.identity.gender}")
if self.identity.height:
identity_parts.append(f"身高大约{self.identity.height}厘米")
if self.identity.weight:
identity_parts.append(f"体重大约{self.identity.weight}千克")
if identity_parts:
details_str = "".join(identity_parts)
@@ -252,3 +238,6 @@ class Individuality:
elif factor == "neuroticism":
return self.personality.neuroticism
return None
individuality = Individuality()

View File

@@ -17,9 +17,9 @@ with open(config_path, "r", encoding="utf-8") as f:
config = toml.load(f)
# 现在可以导入src模块
from src.individuality.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa E402
from src.individuality.questionnaire import FACTOR_DESCRIPTIONS # noqa E402
from src.individuality.offline_llm import LLMRequestOff # noqa E402
from individuality.not_using.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa E402
from individuality.not_using.questionnaire import FACTOR_DESCRIPTIONS # noqa E402
from individuality.not_using.offline_llm import LLMRequestOff # noqa E402
# 加载环境变量
env_path = os.path.join(root_path, ".env")

View File

@@ -16,7 +16,7 @@ from .chat.message_receive.storage import MessageStorage
from .config.config import global_config
from .chat.message_receive.bot import chat_bot
from .common.logger_manager import get_logger
from .individuality.individuality import Individuality
from .individuality.individuality import individuality, Individuality
from .common.server import global_server, Server
from rich.traceback import install
from .chat.focus_chat.expressors.exprssion_learner import expression_learner
@@ -30,7 +30,7 @@ logger = get_logger("main")
class MainSystem:
def __init__(self):
self.hippocampus_manager: HippocampusManager = HippocampusManager.get_instance()
self.individuality: Individuality = Individuality.get_instance()
self.individuality: Individuality = individuality
# 使用消息API替代直接的FastAPI实例
from src.common.message import global_api
@@ -91,7 +91,7 @@ class MainSystem:
self.app.register_message_handler(chat_bot.message_process)
# 初始化个体特征
self.individuality.initialize(
await self.individuality.initialize(
bot_nickname=global_config.bot.nickname,
personality_core=global_config.personality.personality_core,
personality_sides=global_config.personality.personality_sides,
@@ -104,9 +104,6 @@ class MainSystem:
)
logger.success("个体特征初始化成功")
# 初始化表达方式
await expression_learner.extract_and_store_personality_expressions()
try:
# 启动全局消息管理器 (负责消息发送/排队)
await message_manager.start()
@@ -169,7 +166,7 @@ class MainSystem:
async def learn_and_store_expression_task():
"""学习并存储表达方式任务"""
while True:
await asyncio.sleep(60)
await asyncio.sleep(global_config.expression.learning_interval)
print("\033[1;32m[表达方式学习]\033[0m 开始学习表达方式...")
await expression_learner.learn_and_store_expression()
print("\033[1;32m[表达方式学习]\033[0m 表达方式学习完成")

View File

@@ -7,7 +7,7 @@ from typing import Dict, Tuple
from ..config.config import global_config
from ..common.logger_manager import get_logger
from ..manager.async_task_manager import AsyncTask
from ..individuality.individuality import Individuality
from ..individuality.individuality import individuality
logger = get_logger("mood")
@@ -54,7 +54,7 @@ class MoodUpdateTask(AsyncTask):
agreeableness_bias = 0 # 宜人性偏置
neuroticism_factor = 0.5 # 神经质系数
# 获取人格特质
personality = Individuality.get_instance().personality
personality = individuality.personality
if personality:
# 神经质:影响情绪变化速度
neuroticism_factor = 1 + (personality.neuroticism - 0.5) * 0.4