Merge pull request #11 from Dax233/PFC-config-huge-update

Pfc config huge update
pull/937/head
SmartMita 2025-05-13 18:37:10 +09:00 committed by GitHub
commit 2ce30c519a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1717 additions and 1036 deletions

4
.gitignore vendored
View File

@ -13,6 +13,7 @@ llm_tool_benchmark_results.json
MaiBot-Napcat-Adapter-main MaiBot-Napcat-Adapter-main
MaiBot-Napcat-Adapter MaiBot-Napcat-Adapter
/test /test
/log_debug
/src/test /src/test
nonebot-maibot-adapter/ nonebot-maibot-adapter/
*.zip *.zip
@ -35,7 +36,6 @@ config/bot_config.toml
config/bot_config.toml.bak config/bot_config.toml.bak
config/lpmm_config.toml config/lpmm_config.toml
config/lpmm_config.toml.bak config/lpmm_config.toml.bak
src/plugins/remote/client_uuid.json
(测试版)麦麦生成人格.bat (测试版)麦麦生成人格.bat
(临时版)麦麦开始学习.bat (临时版)麦麦开始学习.bat
src/plugins/utils/statistic.py src/plugins/utils/statistic.py
@ -43,7 +43,7 @@ src/plugins/utils/statistic.py
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
llm_statistics.txt maibot_statistics.html
mongodb mongodb
napcat napcat
run_dev.bat run_dev.bat

43
bot.py
View File

@ -1,7 +1,6 @@
import asyncio import asyncio
import hashlib import hashlib
import os import os
import shutil
import sys import sys
from pathlib import Path from pathlib import Path
import time import time
@ -17,6 +16,8 @@ from rich.traceback import install
from src.plugins.group_nickname.nickname_manager import nickname_manager from src.plugins.group_nickname.nickname_manager import nickname_manager
import atexit import atexit
from src.manager.async_task_manager import async_task_manager
install(extra_lines=3) install(extra_lines=3)
# 设置工作目录为脚本所在目录 # 设置工作目录为脚本所在目录
@ -66,38 +67,6 @@ def easter_egg():
print(rainbow_text) print(rainbow_text)
def init_config():
# 初次启动检测
if not os.path.exists("config/bot_config.toml"):
logger.warning("检测到bot_config.toml不存在正在从模板复制")
# 检查config目录是否存在
if not os.path.exists("config"):
os.makedirs("config")
logger.info("创建config目录")
shutil.copy("template/bot_config_template.toml", "config/bot_config.toml")
logger.info("复制完成请修改config/bot_config.toml和.env中的配置后重新启动")
if not os.path.exists("config/lpmm_config.toml"):
logger.warning("检测到lpmm_config.toml不存在正在从模板复制")
# 检查config目录是否存在
if not os.path.exists("config"):
os.makedirs("config")
logger.info("创建config目录")
shutil.copy("template/lpmm_config_template.toml", "config/lpmm_config.toml")
logger.info("复制完成请修改config/lpmm_config.toml和.env中的配置后重新启动")
def init_env():
# 检测.env文件是否存在
if not os.path.exists(".env"):
logger.error("检测到.env文件不存在")
shutil.copy("template/template.env", "./.env")
logger.info("已从template/template.env复制创建.env请修改配置后重新启动")
def load_env(): def load_env():
# 直接加载生产环境变量配置 # 直接加载生产环境变量配置
if os.path.exists(".env"): if os.path.exists(".env"):
@ -142,6 +111,10 @@ def scan_provider(env_config: dict):
async def graceful_shutdown(): async def graceful_shutdown():
try: try:
logger.info("正在优雅关闭麦麦...") logger.info("正在优雅关闭麦麦...")
# 停止所有异步任务
await async_task_manager.stop_and_wait_all_tasks()
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for task in tasks: for task in tasks:
task.cancel() task.cancel()
@ -237,9 +210,9 @@ def raw_main():
check_eula() check_eula()
print("检查EULA和隐私条款完成") print("检查EULA和隐私条款完成")
easter_egg() easter_egg()
init_config()
init_env()
load_env() load_env()
env_config = {key: os.getenv(key) for key in os.environ} env_config = {key: os.getenv(key) for key in os.environ}

View File

@ -1,3 +1,6 @@
# TODO: 更多的可配置项
# TODO: 所有模型单独分离,温度可配置
# TODO: 原生多模态支持
import os import os
import re import re
from dataclasses import dataclass, field from dataclasses import dataclass, field
@ -277,18 +280,44 @@ class BotConfig:
# experimental # experimental
enable_friend_chat: bool = False # 是否启用好友聊天 enable_friend_chat: bool = False # 是否启用好友聊天
# enable_think_flow: bool = False # 是否启用思考流程 # enable_think_flow: bool = False # 是否启用思考流程
enable_friend_whitelist: bool = True # 是否启用好友白名单
talk_allowed_private = set() talk_allowed_private = set()
enable_pfc_chatting: bool = False # 是否启用PFC聊天
enable_pfc_reply_checker: bool = True # 是否开启PFC回复检查
pfc_message_buffer_size: int = (
2 # PFC 聊天消息缓冲数量,有利于使聊天节奏更加紧凑流畅,请根据实际 LLM 响应速度进行调整默认2条
)
api_polling_max_retries: int = 3 # 神秘小功能 api_polling_max_retries: int = 3 # 神秘小功能
rename_person: bool = ( rename_person: bool = (
True # 是否启用改名工具,可以让麦麦对唯一名进行更改,可能可以更拟人地称呼他人,但是也可能导致记忆混淆的问题 True # 是否启用改名工具,可以让麦麦对唯一名进行更改,可能可以更拟人地称呼他人,但是也可能导致记忆混淆的问题
) )
# idle_chat # pfc
enable_pfc_chatting: bool = False # 是否启用PFC聊天该功能仅作用于私聊与回复模式独立
pfc_message_buffer_size: int = (
2 # PFC 聊天消息缓冲数量,有利于使聊天节奏更加紧凑流畅,请根据实际 LLM 响应速度进行调整默认2条
)
pfc_recent_history_display_count: int = 20 # PFC 对话最大可见上下文
# pfc.checker
enable_pfc_reply_checker: bool = True # 是否启用 PFC 的回复检查器
pfc_max_reply_attempts: int = 3 # 发言最多尝试次数
pfc_max_chat_history_for_checker: int = 50 # checker聊天记录最大可见上文长度
# pfc.emotion
pfc_emotion_update_intensity: float = 0.6 # 情绪更新强度
pfc_emotion_history_count: int = 5 # 情绪更新最大可见上下文长度
# pfc.relationship
pfc_relationship_incremental_interval: int = 10 # 关系值增值强度
pfc_relationship_incremental_msg_count: int = 10 # 会话中,关系值判断最大可见上下文
pfc_relationship_incremental_default_change: float = (
1.0 # 会话中,关系值默认更新值(当 llm 返回错误时默认采用该值)
)
pfc_relationship_incremental_max_change: float = 5.0 # 会话中,关系值最大可变值
pfc_relationship_final_msg_count: int = 30 # 会话结束时,关系值判断最大可见上下文
pfc_relationship_final_default_change: float = 5.0 # 会话结束时,关系值默认更新值
pfc_relationship_final_max_change: float = 50.0 # 会话结束时,关系值最大可变值
# pfc.fallback
pfc_historical_fallback_exclude_seconds: int = 7200 # pfc 翻看聊天记录排除最近时长
# pfc.idle_chat
enable_idle_chat: bool = False # 是否启用 pfc 主动发言 enable_idle_chat: bool = False # 是否启用 pfc 主动发言
idle_check_interval: int = 10 # 检查间隔10分钟检查一次 idle_check_interval: int = 10 # 检查间隔10分钟检查一次
min_cooldown: int = 7200 # 最短冷却时间2小时 (7200秒) min_cooldown: int = 7200 # 最短冷却时间2小时 (7200秒)
@ -301,6 +330,7 @@ class BotConfig:
nickname_queue_max_size: int = 100 # 绰号处理队列最大容量 nickname_queue_max_size: int = 100 # 绰号处理队列最大容量
nickname_process_sleep_interval: float = 5 # 绰号处理进程休眠间隔(秒) nickname_process_sleep_interval: float = 5 # 绰号处理进程休眠间隔(秒)
nickname_analysis_history_limit: int = 30 # 绰号处理可见最大上下文 nickname_analysis_history_limit: int = 30 # 绰号处理可见最大上下文
nickname_analysis_probability: float = 0.1 # 绰号随机概率命中,该值越大,绰号分析越频繁
# 模型配置 # 模型配置
llm_reasoning: dict[str, str] = field(default_factory=lambda: {}) llm_reasoning: dict[str, str] = field(default_factory=lambda: {})
@ -458,6 +488,9 @@ class BotConfig:
config.nickname_analysis_history_limit = group_nickname_config.get( config.nickname_analysis_history_limit = group_nickname_config.get(
"nickname_analysis_history_limit", config.nickname_analysis_history_limit "nickname_analysis_history_limit", config.nickname_analysis_history_limit
) )
config.nickname_analysis_probability = group_nickname_config.get(
"nickname_analysis_probability", config.nickname_analysis_probability
)
def bot(parent: dict): def bot(parent: dict):
# 机器人基础配置 # 机器人基础配置
@ -547,10 +580,10 @@ class BotConfig:
"llm_heartflow", "llm_heartflow",
"llm_PFC_action_planner", "llm_PFC_action_planner",
"llm_PFC_chat", "llm_PFC_chat",
"llm_PFC_relationship_eval",
"llm_nickname_mapping", "llm_nickname_mapping",
"llm_scheduler_all", "llm_scheduler_all",
"llm_scheduler_doing", "llm_scheduler_doing",
"llm_PFC_relationship_eval",
] ]
for item in config_list: for item in config_list:
@ -724,30 +757,97 @@ class BotConfig:
config.enable_friend_chat = experimental_config.get("enable_friend_chat", config.enable_friend_chat) config.enable_friend_chat = experimental_config.get("enable_friend_chat", config.enable_friend_chat)
# config.enable_think_flow = experimental_config.get("enable_think_flow", config.enable_think_flow) # config.enable_think_flow = experimental_config.get("enable_think_flow", config.enable_think_flow)
config.talk_allowed_private = set(str(user) for user in experimental_config.get("talk_allowed_private", [])) 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"): if config.INNER_VERSION in SpecifierSet(">=1.6.2.4"):
config.enable_pfc_chatting = experimental_config.get("pfc_chatting", config.enable_pfc_chatting) config.enable_friend_whitelist = experimental_config.get(
"enable_friend_whitelist", config.enable_friend_whitelist
)
if config.INNER_VERSION in SpecifierSet(">=1.6.1.5"): if config.INNER_VERSION in SpecifierSet(">=1.6.1.5"):
config.api_polling_max_retries = experimental_config.get( config.api_polling_max_retries = experimental_config.get(
"api_polling_max_retries", config.api_polling_max_retries "api_polling_max_retries", config.api_polling_max_retries
) )
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
)
logger.info(f"PFC Reply Checker 状态: {'启用' if config.enable_pfc_reply_checker else '关闭'}")
config.pfc_message_buffer_size = experimental_config.get(
"pfc_message_buffer_size", config.pfc_message_buffer_size
)
if config.INNER_VERSION in SpecifierSet(">=1.6.2.3"): if config.INNER_VERSION in SpecifierSet(">=1.6.2.3"):
config.rename_person = experimental_config.get("rename_person", config.rename_person) config.rename_person = experimental_config.get("rename_person", config.rename_person)
def idle_chat(parent: dict): def pfc(parent: dict):
idle_chat_config = parent["idle_chat"] if config.INNER_VERSION in SpecifierSet(">=1.6.2.4"):
if config.INNER_VERSION in SpecifierSet(">=1.6.1.6"): pfc_config = parent.get("pfc", {})
config.enable_idle_chat = idle_chat_config.get("enable_idle_chat", config.enable_idle_chat) # 解析 [pfc] 下的直接字段
config.idle_check_interval = idle_chat_config.get("idle_check_interval", config.idle_check_interval) config.enable_pfc_chatting = pfc_config.get("enable_pfc_chatting", config.enable_pfc_chatting)
config.min_cooldown = idle_chat_config.get("min_cooldown", config.min_cooldown) config.pfc_message_buffer_size = pfc_config.get(
config.max_cooldown = idle_chat_config.get("max_cooldown", config.max_cooldown) "pfc_message_buffer_size", config.pfc_message_buffer_size
)
config.pfc_recent_history_display_count = pfc_config.get(
"pfc_recent_history_display_count", config.pfc_recent_history_display_count
)
# 解析 [[pfc.checker]] 子表
checker_list = pfc_config.get("checker", [])
if checker_list and isinstance(checker_list, list):
checker_config = checker_list[0] if checker_list else {}
config.enable_pfc_reply_checker = checker_config.get(
"enable_pfc_reply_checker", config.enable_pfc_reply_checker
)
config.pfc_max_reply_attempts = checker_config.get(
"pfc_max_reply_attempts", config.pfc_max_reply_attempts
)
config.pfc_max_chat_history_for_checker = checker_config.get(
"pfc_max_chat_history_for_checker", config.pfc_max_chat_history_for_checker
)
# 解析 [[pfc.emotion]] 子表
emotion_list = pfc_config.get("emotion", [])
if emotion_list and isinstance(emotion_list, list):
emotion_config = emotion_list[0] if emotion_list else {}
config.pfc_emotion_update_intensity = emotion_config.get(
"pfc_emotion_update_intensity", config.pfc_emotion_update_intensity
)
config.pfc_emotion_history_count = emotion_config.get(
"pfc_emotion_history_count", config.pfc_emotion_history_count
)
# 解析 [[pfc.relationship]] 子表
relationship_list = pfc_config.get("relationship", [])
if relationship_list and isinstance(relationship_list, list):
relationship_config = relationship_list[0] if relationship_list else {}
config.pfc_relationship_incremental_interval = relationship_config.get(
"pfc_relationship_incremental_interval", config.pfc_relationship_incremental_interval
)
config.pfc_relationship_incremental_msg_count = relationship_config.get(
"pfc_relationship_incremental_msg_count", config.pfc_relationship_incremental_msg_count
)
config.pfc_relationship_incremental_default_change = relationship_config.get(
"pfc_relationship_incremental_default_change",
config.pfc_relationship_incremental_default_change,
)
config.pfc_relationship_incremental_max_change = relationship_config.get(
"pfc_relationship_incremental_max_change", config.pfc_relationship_incremental_max_change
)
config.pfc_relationship_final_msg_count = relationship_config.get(
"pfc_relationship_final_msg_count", config.pfc_relationship_final_msg_count
)
config.pfc_relationship_final_default_change = relationship_config.get(
"pfc_relationship_final_default_change", config.pfc_relationship_final_default_change
)
config.pfc_relationship_final_max_change = relationship_config.get(
"pfc_relationship_final_max_change", config.pfc_relationship_final_max_change
)
# 解析 [[pfc.fallback]] 子表
fallback_list = pfc_config.get("fallback", [])
if fallback_list and isinstance(fallback_list, list):
fallback_config = fallback_list[0] if fallback_list else {}
config.pfc_historical_fallback_exclude_seconds = fallback_config.get(
"pfc_historical_fallback_exclude_seconds", config.pfc_historical_fallback_exclude_seconds
)
# 解析 [[pfc.idle_chat]] 子表
idle_chat_list = pfc_config.get("idle_chat", [])
if idle_chat_list and isinstance(idle_chat_list, list):
idle_chat_config = idle_chat_list[0] if idle_chat_list else {}
config.enable_idle_chat = idle_chat_config.get("enable_idle_chat", config.enable_idle_chat)
config.idle_check_interval = idle_chat_config.get("idle_check_interval", config.idle_check_interval)
config.min_cooldown = idle_chat_config.get("min_cooldown", config.min_cooldown)
config.max_cooldown = idle_chat_config.get("max_cooldown", config.max_cooldown)
# 版本表达式:>=1.0.0,<2.0.0 # 版本表达式:>=1.0.0,<2.0.0
# 允许字段func: method, support: str, notice: str, necessary: bool # 允许字段func: method, support: str, notice: str, necessary: bool
@ -783,7 +883,7 @@ class BotConfig:
"normal_chat": {"func": normal_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}, "focus_chat": {"func": focus_chat, "support": ">=1.6.0", "necessary": False},
"group_nickname": {"func": group_nickname, "support": ">=1.6.1.1", "necessary": False}, "group_nickname": {"func": group_nickname, "support": ">=1.6.1.1", "necessary": False},
"idle_chat": {"func": idle_chat, "support": ">=1.6.1.6", "necessary": False}, "pfc": {"func": pfc, "support": ">=1.6.2.4", "necessary": False},
} }
# 原地修改,将 字符串版本表达式 转换成 版本对象 # 原地修改,将 字符串版本表达式 转换成 版本对象

View File

@ -1,10 +1,10 @@
from src.do_tool.tool_can_use.base_tool import BaseTool
from src.config.config import global_config
from src.common.logger_manager import get_logger
from src.plugins.moods.moods import MoodManager
from typing import Any from typing import Any
from src.common.logger_manager import get_logger
from src.config.config import global_config
from src.do_tool.tool_can_use.base_tool import BaseTool
from src.manager.mood_manager import mood_manager
logger = get_logger("change_mood_tool") logger = get_logger("change_mood_tool")
@ -36,7 +36,6 @@ class ChangeMoodTool(BaseTool):
response_set = function_args.get("response_set") response_set = function_args.get("response_set")
_message_processed_plain_text = function_args.get("text") _message_processed_plain_text = function_args.get("text")
mood_manager = MoodManager.get_instance()
# gpt = ResponseGenerator() # gpt = ResponseGenerator()
if response_set is None: if response_set is None:

View File

@ -1,4 +1,4 @@
from src.plugins.moods.moods import MoodManager from src.manager.mood_manager import mood_manager
import enum import enum
@ -13,5 +13,5 @@ class ChatStateInfo:
self.chat_status: ChatState = ChatState.ABSENT self.chat_status: ChatState = ChatState.ABSENT
self.current_state_time = 120 self.current_state_time = 120
self.mood_manager = MoodManager() self.mood_manager = mood_manager
self.mood = self.mood_manager.get_prompt() self.mood = self.mood_manager.get_mood_prompt()

View File

@ -3,7 +3,7 @@ import time
import random import random
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.plugins.moods.moods import MoodManager from src.manager.mood_manager import mood_manager
from src.config.config import global_config from src.config.config import global_config
logger = get_logger("mai_state") logger = get_logger("mai_state")
@ -88,7 +88,7 @@ class MaiStateInfo:
self.last_min_check_time: float = time.time() # 上次1分钟规则检查时间 self.last_min_check_time: float = time.time() # 上次1分钟规则检查时间
# Mood management is now part of MaiStateInfo # Mood management is now part of MaiStateInfo
self.mood_manager = MoodManager.get_instance() # Use singleton instance self.mood_manager = mood_manager # Use singleton instance
def update_mai_status(self, new_status: MaiState) -> bool: def update_mai_status(self, new_status: MaiState) -> bool:
""" """
@ -124,7 +124,7 @@ class MaiStateInfo:
def get_mood_prompt(self) -> str: def get_mood_prompt(self) -> str:
"""获取当前的心情提示词""" """获取当前的心情提示词"""
# Delegate to the internal mood manager # Delegate to the internal mood manager
return self.mood_manager.get_prompt() return self.mood_manager.get_mood_prompt()
def get_current_state(self) -> MaiState: def get_current_state(self) -> MaiState:
"""获取当前的 MaiState""" """获取当前的 MaiState"""

View File

@ -1,7 +1,12 @@
import asyncio import asyncio
import time import time
from .plugins.utils.statistic import LLMStatistics
from .plugins.moods.moods import MoodManager from maim_message import MessageServer
from .plugins.remote.remote import TelemetryHeartBeatTask
from .manager.async_task_manager import async_task_manager
from .plugins.utils.statistic import OnlineTimeRecordTask, StatisticOutputTask
from .manager.mood_manager import MoodPrintTask, MoodUpdateTask
from .plugins.schedule.schedule_generator import bot_schedule from .plugins.schedule.schedule_generator import bot_schedule
from .plugins.emoji_system.emoji_manager import emoji_manager from .plugins.emoji_system.emoji_manager import emoji_manager
from .plugins.person_info.person_info import person_info_manager from .plugins.person_info.person_info import person_info_manager
@ -14,9 +19,8 @@ from .plugins.storage.storage import MessageStorage
from .config.config import global_config from .config.config import global_config
from .plugins.chat.bot import chat_bot from .plugins.chat.bot import chat_bot
from .common.logger_manager import get_logger from .common.logger_manager import get_logger
from .plugins.remote import heartbeat_thread # noqa: F401
from .individuality.individuality import Individuality from .individuality.individuality import Individuality
from .common.server import global_server from .common.server import global_server, Server
from rich.traceback import install from rich.traceback import install
from .api.main import start_api_server from .api.main import start_api_server
@ -27,17 +31,14 @@ logger = get_logger("main")
class MainSystem: class MainSystem:
def __init__(self): def __init__(self):
self.llm_stats = LLMStatistics("llm_statistics.txt") self.hippocampus_manager: HippocampusManager = HippocampusManager.get_instance()
self.mood_manager = MoodManager.get_instance() self.individuality: Individuality = Individuality.get_instance()
self.hippocampus_manager = HippocampusManager.get_instance()
self._message_manager_started = False
self.individuality = Individuality.get_instance()
# 使用消息API替代直接的FastAPI实例 # 使用消息API替代直接的FastAPI实例
from .plugins.message import global_api from .plugins.message import global_api
self.app = global_api self.app: MessageServer = global_api
self.server = global_server self.server: Server = global_server
async def initialize(self): async def initialize(self):
"""初始化系统组件""" """初始化系统组件"""
@ -51,9 +52,15 @@ class MainSystem:
async def _init_components(self): async def _init_components(self):
"""初始化其他组件""" """初始化其他组件"""
init_start_time = time.time() init_start_time = time.time()
# 启动LLM统计
self.llm_stats.start() # 添加在线时间统计任务
logger.success("LLM统计功能启动成功") await async_task_manager.add_task(OnlineTimeRecordTask())
# 添加统计信息输出任务
await async_task_manager.add_task(StatisticOutputTask())
# 添加遥测心跳任务
await async_task_manager.add_task(TelemetryHeartBeatTask())
# 启动API服务器 # 启动API服务器
start_api_server() start_api_server()
@ -62,9 +69,10 @@ class MainSystem:
emoji_manager.initialize() emoji_manager.initialize()
logger.success("表情包管理器初始化成功") logger.success("表情包管理器初始化成功")
# 启动情绪管理器 # 添加情绪衰减任务
self.mood_manager.start_mood_update(update_interval=global_config.mood_update_interval) await async_task_manager.add_task(MoodUpdateTask())
logger.success("情绪管理器启动成功") # 添加情绪打印任务
await async_task_manager.add_task(MoodPrintTask())
# 检查并清除person_info冗余字段启动个人习惯推断 # 检查并清除person_info冗余字段启动个人习惯推断
await person_info_manager.del_all_undefined_field() await person_info_manager.del_all_undefined_field()
@ -129,7 +137,6 @@ class MainSystem:
self.build_memory_task(), self.build_memory_task(),
self.forget_memory_task(), self.forget_memory_task(),
self.consolidate_memory_task(), self.consolidate_memory_task(),
self.print_mood_task(),
self.remove_recalled_message_task(), self.remove_recalled_message_task(),
emoji_manager.start_periodic_check_register(), emoji_manager.start_periodic_check_register(),
self.app.run(), self.app.run(),
@ -163,12 +170,6 @@ class MainSystem:
await HippocampusManager.get_instance().consolidate_memory() await HippocampusManager.get_instance().consolidate_memory()
print("\033[1;32m[记忆整合]\033[0m 记忆整合完成") print("\033[1;32m[记忆整合]\033[0m 记忆整合完成")
async def print_mood_task(self):
"""打印情绪状态"""
while True:
self.mood_manager.print_mood_status()
await asyncio.sleep(60)
@staticmethod @staticmethod
async def remove_recalled_message_task(): async def remove_recalled_message_task():
"""删除撤回消息任务""" """删除撤回消息任务"""

View File

@ -0,0 +1,150 @@
from abc import abstractmethod
import asyncio
from asyncio import Task, Event, Lock
from typing import Callable, Dict
from src.common.logger_manager import get_logger
logger = get_logger("async_task_manager")
class AsyncTask:
"""异步任务基类"""
def __init__(self, task_name: str | None = None, wait_before_start: int = 0, run_interval: int = 0):
self.task_name: str = task_name or self.__class__.__name__
"""任务名称"""
self.wait_before_start: int = wait_before_start
"""运行任务前是否进行等待单位设为0则不等待"""
self.run_interval: int = run_interval
"""多次运行的时间间隔单位设为0则仅运行一次"""
@abstractmethod
async def run(self):
"""
任务的执行过程
"""
pass
async def start_task(self, abort_flag: asyncio.Event):
if self.wait_before_start > 0:
# 等待指定时间后开始任务
await asyncio.sleep(self.wait_before_start)
while not abort_flag.is_set():
await self.run()
if self.run_interval > 0:
await asyncio.sleep(self.run_interval)
else:
break
class AsyncTaskManager:
"""异步任务管理器"""
def __init__(self):
self.tasks: Dict[str, Task] = {}
"""任务列表"""
self.abort_flag: Event = Event()
"""是否中止任务标志"""
self._lock: Lock = Lock()
"""异步锁当可能出现await时需要加锁"""
def _remove_task_call_back(self, task: Task):
"""
call_back: 任务完成后移除任务
"""
task_name = task.get_name()
if task_name in self.tasks:
# 任务完成后移除任务
del self.tasks[task_name]
logger.debug(f"已移除任务 '{task_name}'")
else:
logger.warning(f"尝试移除不存在的任务 '{task_name}'")
@staticmethod
def _default_finish_call_back(task: Task):
"""
call_back: 默认的任务完成回调函数
"""
try:
task.result()
logger.debug(f"任务 '{task.get_name()}' 完成")
except asyncio.CancelledError:
logger.debug(f"任务 '{task.get_name()}' 被取消")
except Exception as e:
logger.error(f"任务 '{task.get_name()}' 执行时发生异常: {e}", exc_info=True)
async def add_task(self, task: AsyncTask, call_back: Callable[[asyncio.Task], None] | None = None):
"""
添加任务
"""
if not issubclass(task.__class__, AsyncTask):
raise TypeError(f"task '{task.__class__.__name__}' 必须是继承 AsyncTask 的子类")
async with self._lock: # 由于可能需要await等待任务完成所以需要加异步锁
if task.task_name in self.tasks:
logger.warning(f"已存在名称为 '{task.task_name}' 的任务,正在尝试取消并替换")
self.tasks[task.task_name].cancel() # 取消已存在的任务
await self.tasks[task.task_name] # 等待任务完成
logger.info(f"成功结束任务 '{task.task_name}'")
# 创建新任务
task_inst = asyncio.create_task(task.start_task(self.abort_flag))
task_inst.set_name(task.task_name)
task_inst.add_done_callback(self._remove_task_call_back) # 添加完成回调函数-完成任务后自动移除任务
task_inst.add_done_callback(
call_back or self._default_finish_call_back
) # 添加完成回调函数-用户自定义或默认的FallBack
self.tasks[task.task_name] = task_inst # 将任务添加到任务列表
logger.info(f"已启动任务 '{task.task_name}'")
def get_tasks_status(self) -> Dict[str, Dict[str, str]]:
"""
获取所有任务的状态
"""
tasks_status = {}
for task_name, task in self.tasks.items():
tasks_status[task_name] = {
"status": "running" if not task.done() else "done",
}
return tasks_status
async def stop_and_wait_all_tasks(self):
"""
终止所有任务并等待它们完成该方法会阻塞其它尝试add_task()的操作
"""
async with self._lock: # 由于可能需要await等待任务完成所以需要加异步锁
# 设置中止标志
self.abort_flag.set()
# 取消所有任务
for name, inst in self.tasks.items():
try:
inst.cancel()
except asyncio.CancelledError:
logger.info(f"已取消任务 '{name}'")
# 等待所有任务完成
for task_name, task_inst in self.tasks.items():
if not task_inst.done():
try:
await task_inst
except asyncio.CancelledError: # 此处再次捕获取消异常防止stop_all_tasks()时延迟抛出异常
logger.info(f"任务 {task_name} 已取消")
except Exception as e:
logger.error(f"任务 {task_name} 执行时发生异常: {e}", ext_info=True)
# 清空任务列表
self.tasks.clear()
self.abort_flag.clear()
logger.info("所有异步任务已停止")
async_task_manager = AsyncTaskManager()
"""全局异步任务管理器实例"""

View File

@ -0,0 +1,67 @@
import json
import os
from src.common.logger_manager import get_logger
LOCAL_STORE_FILE_PATH = "data/local_store.json"
logger = get_logger("local_storage")
class LocalStoreManager:
file_path: str
"""本地存储路径"""
store: dict[str, str | list | dict | int | float | bool]
"""本地存储数据"""
def __init__(self, local_store_path: str | None = None):
self.file_path = local_store_path or LOCAL_STORE_FILE_PATH
self.store = {}
self.load_local_store()
def __getitem__(self, item: str) -> str | list | dict | int | float | bool | None:
"""获取本地存储数据"""
return self.store.get(item, None)
def __setitem__(self, key: str, value: str | list | dict | int | float | bool):
"""设置本地存储数据"""
self.store[key] = value
self.save_local_store()
def __contains__(self, item: str) -> bool:
"""检查本地存储数据是否存在"""
return item in self.store
def load_local_store(self):
"""加载本地存储数据"""
if os.path.exists(self.file_path):
# 存在本地存储文件,加载数据
logger.info("正在阅读记事本......我在看,我真的在看!")
logger.debug(f"加载本地存储数据: {self.file_path}")
try:
with open(self.file_path, "r", encoding="utf-8") as f:
self.store = json.load(f)
logger.success("全都记起来了!")
except json.JSONDecodeError:
logger.warning("啊咧?记事本被弄脏了,正在重建记事本......")
self.store = {}
with open(self.file_path, "w", encoding="utf-8") as f:
json.dump({}, f, ensure_ascii=False, indent=4)
logger.success("记事本重建成功!")
else:
# 不存在本地存储文件,创建新的目录和文件
logger.warning("啊咧?记事本不存在,正在创建新的记事本......")
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
with open(self.file_path, "w", encoding="utf-8") as f:
json.dump({}, f, ensure_ascii=False, indent=4)
logger.success("记事本创建成功!")
def save_local_store(self):
"""保存本地存储数据"""
logger.debug(f"保存本地存储数据: {self.file_path}")
with open(self.file_path, "w", encoding="utf-8") as f:
json.dump(self.store, f, ensure_ascii=False, indent=4)
local_storage = LocalStoreManager("data/local_store.json") # 全局单例化

View File

@ -0,0 +1,296 @@
import asyncio
import math
import time
from dataclasses import dataclass
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
logger = get_logger("mood")
@dataclass
class MoodState:
valence: float
"""愉悦度 (-1.0 到 1.0)-1表示极度负面1表示极度正面"""
arousal: float
"""唤醒度 (-1.0 到 1.0)-1表示抑制1表示兴奋"""
text: str
"""心情的文本描述"""
@dataclass
class MoodChangeHistory:
valence_direction_factor: int
"""愉悦度变化的系数(正为增益,负为抑制)"""
arousal_direction_factor: int
"""唤醒度变化的系数(正为增益,负为抑制)"""
class MoodUpdateTask(AsyncTask):
def __init__(self):
super().__init__(
task_name="Mood Update Task",
wait_before_start=global_config.mood_update_interval,
run_interval=global_config.mood_update_interval,
)
# 从配置文件获取衰减率
self.decay_rate_valence: float = 1 - global_config.mood_decay_rate
"""愉悦度衰减率"""
self.decay_rate_arousal: float = 1 - global_config.mood_decay_rate
"""唤醒度衰减率"""
self.last_update = time.time()
"""上次更新时间"""
async def run(self):
current_time = time.time()
time_diff = current_time - self.last_update
agreeableness_factor = 1 # 宜人性系数
agreeableness_bias = 0 # 宜人性偏置
neuroticism_factor = 0.5 # 神经质系数
# 获取人格特质
personality = Individuality.get_instance().personality
if personality:
# 神经质:影响情绪变化速度
neuroticism_factor = 1 + (personality.neuroticism - 0.5) * 0.4
agreeableness_factor = 1 + (personality.agreeableness - 0.5) * 0.4
# 宜人性:影响情绪基准线
if personality.agreeableness < 0.2:
agreeableness_bias = (personality.agreeableness - 0.2) * 0.5
elif personality.agreeableness > 0.8:
agreeableness_bias = (personality.agreeableness - 0.8) * 0.5
else:
agreeableness_bias = 0
# 分别计算正向和负向的衰减率
if mood_manager.current_mood.valence >= 0:
# 正向情绪衰减
decay_rate_positive = self.decay_rate_valence * (1 / agreeableness_factor)
valence_target = 0 + agreeableness_bias
new_valence = valence_target + (mood_manager.current_mood.valence - valence_target) * math.exp(
-decay_rate_positive * time_diff * neuroticism_factor
)
else:
# 负向情绪衰减
decay_rate_negative = self.decay_rate_valence * agreeableness_factor
valence_target = 0 + agreeableness_bias
new_valence = valence_target + (mood_manager.current_mood.valence - valence_target) * math.exp(
-decay_rate_negative * time_diff * neuroticism_factor
)
# Arousal 向中性0回归
arousal_target = 0
new_arousal = arousal_target + (mood_manager.current_mood.arousal - arousal_target) * math.exp(
-self.decay_rate_arousal * time_diff * neuroticism_factor
)
mood_manager.set_current_mood(new_valence, new_arousal)
self.last_update = current_time
class MoodPrintTask(AsyncTask):
def __init__(self):
super().__init__(
task_name="Mood Print Task",
wait_before_start=60,
run_interval=60,
)
async def run(self):
# 打印当前心情
logger.info(
f"愉悦度: {mood_manager.current_mood.valence:.2f}, "
f"唤醒度: {mood_manager.current_mood.arousal:.2f}, "
f"心情: {mood_manager.current_mood.text}"
)
class MoodManager:
# TODO: 改进,使用具有实验支持的新情绪模型
EMOTION_FACTOR_MAP: Dict[str, Tuple[float, float]] = {
"开心": (0.21, 0.6),
"害羞": (0.15, 0.2),
"愤怒": (-0.24, 0.8),
"恐惧": (-0.21, 0.7),
"悲伤": (-0.21, 0.3),
"厌恶": (-0.12, 0.4),
"惊讶": (0.06, 0.7),
"困惑": (0.0, 0.6),
"平静": (0.03, 0.5),
}
"""
情绪词映射表 {mood: (valence, arousal)}
将情绪描述词映射到愉悦度和唤醒度的元组
"""
EMOTION_POINT_MAP: Dict[Tuple[float, float], str] = {
# 第一象限:高唤醒,正愉悦
(0.5, 0.4): "兴奋",
(0.3, 0.6): "快乐",
(0.2, 0.3): "满足",
# 第二象限:高唤醒,负愉悦
(-0.5, 0.4): "愤怒",
(-0.3, 0.6): "焦虑",
(-0.2, 0.3): "烦躁",
# 第三象限:低唤醒,负愉悦
(-0.5, -0.4): "悲伤",
(-0.3, -0.3): "疲倦",
(-0.4, -0.7): "疲倦",
# 第四象限:低唤醒,正愉悦
(0.2, -0.1): "平静",
(0.3, -0.2): "安宁",
(0.5, -0.4): "放松",
}
"""
情绪文本映射表 {(valence, arousal): mood}
将量化的情绪状态元组映射到文本描述
"""
def __init__(self):
self.current_mood = MoodState(
valence=0.0,
arousal=0.0,
text="平静",
)
"""当前情绪状态"""
self.mood_change_history: MoodChangeHistory = MoodChangeHistory(
valence_direction_factor=0,
arousal_direction_factor=0,
)
"""情绪变化历史"""
self._lock = asyncio.Lock()
"""异步锁,用于保护线程安全"""
def set_current_mood(self, new_valence: float, new_arousal: float):
"""
设置当前情绪状态
:param new_valence: 新的愉悦度
:param new_arousal: 新的唤醒度
"""
# 限制范围
self.current_mood.valence = max(-1.0, min(new_valence, 1.0))
self.current_mood.arousal = max(-1.0, min(new_arousal, 1.0))
closest_mood = None
min_distance = float("inf")
for (v, a), text in self.EMOTION_POINT_MAP.items():
# 计算当前情绪状态与每个情绪文本的欧氏距离
distance = math.sqrt((self.current_mood.valence - v) ** 2 + (self.current_mood.arousal - a) ** 2)
if distance < min_distance:
min_distance = distance
closest_mood = text
if closest_mood:
self.current_mood.text = closest_mood
def update_current_mood(self, valence_delta: float, arousal_delta: float):
"""
根据愉悦度和唤醒度变化量更新当前情绪状态
:param valence_delta: 愉悦度变化量
:param arousal_delta: 唤醒度变化量
"""
# 计算连续增益/抑制
# 规则:多次相同方向的变化会有更大的影响系数,反方向的变化会清零影响系数(系数的正负号由变化方向决定)
if valence_delta * self.mood_change_history.valence_direction_factor > 0:
# 如果方向相同,则根据变化方向改变系数
if valence_delta > 0:
self.mood_change_history.valence_direction_factor += 1 # 若为正向,则增加
else:
self.mood_change_history.valence_direction_factor -= 1 # 若为负向,则减少
else:
# 如果方向不同,则重置计数
self.mood_change_history.valence_direction_factor = 0
if arousal_delta * self.mood_change_history.arousal_direction_factor > 0:
# 如果方向相同,则根据变化方向改变系数
if arousal_delta > 0:
self.mood_change_history.arousal_direction_factor += 1 # 若为正向,则增加计数
else:
self.mood_change_history.arousal_direction_factor -= 1 # 若为负向,则减少计数
else:
# 如果方向不同,则重置计数
self.mood_change_history.arousal_direction_factor = 0
# 计算增益/抑制的结果
# 规则:如果当前情绪状态与变化方向相同,则增益;否则抑制
if self.current_mood.valence * self.mood_change_history.valence_direction_factor > 0:
valence_delta = valence_delta * (1.01 ** abs(self.mood_change_history.valence_direction_factor))
else:
valence_delta = valence_delta * (0.99 ** abs(self.mood_change_history.valence_direction_factor))
if self.current_mood.arousal * self.mood_change_history.arousal_direction_factor > 0:
arousal_delta = arousal_delta * (1.01 ** abs(self.mood_change_history.arousal_direction_factor))
else:
arousal_delta = arousal_delta * (0.99 ** abs(self.mood_change_history.arousal_direction_factor))
self.set_current_mood(
new_valence=self.current_mood.valence + valence_delta,
new_arousal=self.current_mood.arousal + arousal_delta,
)
def get_mood_prompt(self) -> str:
"""
根据当前情绪状态生成提示词
"""
base_prompt = f"当前心情:{self.current_mood.text}"
# 根据情绪状态添加额外的提示信息
if self.current_mood.valence > 0.5:
base_prompt += "你现在心情很好,"
elif self.current_mood.valence < -0.5:
base_prompt += "你现在心情不太好,"
if self.current_mood.arousal > 0.4:
base_prompt += "情绪比较激动。"
elif self.current_mood.arousal < -0.4:
base_prompt += "情绪比较平静。"
return base_prompt
def get_arousal_multiplier(self) -> float:
"""
根据当前情绪状态返回唤醒度乘数
"""
if self.current_mood.arousal > 0.4:
multiplier = 1 + min(0.15, (self.current_mood.arousal - 0.4) / 3)
return multiplier
elif self.current_mood.arousal < -0.4:
multiplier = 1 - min(0.15, ((0 - self.current_mood.arousal) - 0.4) / 3)
return multiplier
return 1.0
def update_mood_from_emotion(self, emotion: str, intensity: float = 1.0) -> None:
"""
根据情绪词更新心情状态
:param emotion: 情绪词'开心', '悲伤'等位于self.EMOTION_FACTOR_MAP中的键
:param intensity: 情绪强度0.0-1.0
"""
if emotion not in self.EMOTION_FACTOR_MAP:
logger.error(f"[情绪更新] 未知情绪词: {emotion}")
return
valence_change, arousal_change = self.EMOTION_FACTOR_MAP[emotion]
old_valence = self.current_mood.valence
old_arousal = self.current_mood.arousal
old_mood = self.current_mood.text
self.update_current_mood(valence_change, arousal_change) # 更新当前情绪状态
logger.info(
f"[情绪变化] {emotion}(强度:{intensity:.2f}) | 愉悦度:{old_valence:.2f}->{self.current_mood.valence:.2f}, 唤醒度:{old_arousal:.2f}->{self.current_mood.arousal:.2f} | 心情:{old_mood}->{self.current_mood.text}"
)
mood_manager = MoodManager()
"""全局情绪管理器"""

View File

@ -1,3 +1,5 @@
# TODO: 开机自启,遍历所有可发起的聊天流,而不是等待 PFC 实例结束
# TODO: 优化 idle 逻辑 增强其与 PFC 模式的联动
from typing import Optional, Dict, Set from typing import Optional, Dict, Set
import asyncio import asyncio
import time import time

View File

@ -11,7 +11,7 @@ from rich.traceback import install
install(extra_lines=3) install(extra_lines=3)
logger = get_module_logger("chat_observer") logger = get_module_logger("pfc_chat_observer")
class ChatObserver: class ChatObserver:

View File

@ -10,7 +10,7 @@ from ..chat.message import Message # 假设 Message 类型被 _convert_to_messa
from src.config.config import global_config from src.config.config import global_config
from ..person_info.person_info import person_info_manager from ..person_info.person_info import person_info_manager
from ..person_info.relationship_manager import relationship_manager from ..person_info.relationship_manager import relationship_manager
from ..moods.moods import MoodManager from src.manager.mood_manager import mood_manager
from .pfc_relationship import PfcRelationshipUpdater, PfcRepationshipTranslator from .pfc_relationship import PfcRelationshipUpdater, PfcRepationshipTranslator
from .pfc_emotion import PfcEmotionUpdater from .pfc_emotion import PfcEmotionUpdater
@ -57,7 +57,7 @@ class Conversation:
self.person_info_mng = person_info_manager self.person_info_mng = person_info_manager
self.relationship_mng = relationship_manager self.relationship_mng = relationship_manager
self.mood_mng = MoodManager.get_instance() self.mood_mng = mood_manager
self.relationship_updater: Optional[PfcRelationshipUpdater] = None self.relationship_updater: Optional[PfcRelationshipUpdater] = None
self.relationship_translator: Optional[PfcRepationshipTranslator] = None self.relationship_translator: Optional[PfcRepationshipTranslator] = None
@ -138,9 +138,6 @@ class Conversation:
logger.debug(f"[私聊][{self.private_name}] 已减少IdleChat活跃实例计数") logger.debug(f"[私聊][{self.private_name}] 已减少IdleChat活跃实例计数")
if self.observation_info and self.chat_observer: # 确保二者都存在 if self.observation_info and self.chat_observer: # 确保二者都存在
self.observation_info.unbind_from_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.debug(f"[私聊][{self.private_name}] MoodManager 后台更新已停止。")
self._initialized = False # 标记为未初始化 self._initialized = False # 标记为未初始化
logger.info(f"[私聊][{self.private_name}] 对话实例 {self.stream_id} 已停止。") logger.info(f"[私聊][{self.private_name}] 对话实例 {self.stream_id} 已停止。")

View File

@ -249,22 +249,6 @@ async def initialize_core_components(conversation_instance: "Conversation"):
# 不需要再次启动,只需确保已初始化 # 不需要再次启动,只需确保已初始化
logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) IdleChat实例已初始化") logger.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) IdleChat实例已初始化")
if (
conversation_instance.mood_mng
and hasattr(conversation_instance.mood_mng, "start_mood_update")
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.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.debug(f"[私聊][{conversation_instance.private_name}] (Initializer) MoodManager 已在运行中。")
else:
logger.warning(
f"[私聊][{conversation_instance.private_name}] (Initializer) MoodManager 未能启动,相关功能可能受限。"
)
if ( if (
conversation_instance.conversation_info conversation_instance.conversation_info
and conversation_instance.conversation_info.person_id and conversation_instance.conversation_info.person_id
@ -299,7 +283,7 @@ async def initialize_core_components(conversation_instance: "Conversation"):
if conversation_instance.conversation_info and conversation_instance.mood_mng: # 确保都存在 if conversation_instance.conversation_info and conversation_instance.mood_mng: # 确保都存在
try: try:
conversation_instance.conversation_info.current_emotion_text = ( conversation_instance.conversation_info.current_emotion_text = (
conversation_instance.mood_mng.get_prompt() conversation_instance.mood_mng.get_mood_prompt()
) # type: ignore ) # type: ignore
logger.debug( logger.debug(
f"[私聊][{conversation_instance.private_name}] (Initializer) 初始化时加载情绪文本: {conversation_instance.conversation_info.current_emotion_text}" f"[私聊][{conversation_instance.private_name}] (Initializer) 初始化时加载情绪文本: {conversation_instance.conversation_info.current_emotion_text}"

View File

@ -118,7 +118,7 @@ async def run_conversation_loop(conversation_instance: "Conversation"):
conversation_instance.conversation_info.relationship_text = "你们的关系是:普通。" conversation_instance.conversation_info.relationship_text = "你们的关系是:普通。"
if conversation_instance.mood_mng: if conversation_instance.mood_mng:
conversation_instance.conversation_info.current_emotion_text = ( conversation_instance.conversation_info.current_emotion_text = (
conversation_instance.mood_mng.get_prompt() conversation_instance.mood_mng.get_mood_prompt()
) )
if not all( if not all(

View File

@ -12,7 +12,7 @@ from rich.traceback import install
install(extra_lines=3) install(extra_lines=3)
logger = get_module_logger("message_sender") logger = get_module_logger("pfc_sender")
class DirectMessageSender: class DirectMessageSender:

View File

@ -11,7 +11,7 @@ from src.config.config import global_config
from .chat_observer import ChatObserver from .chat_observer import ChatObserver
from .chat_states import NotificationHandler, NotificationType, Notification from .chat_states import NotificationHandler, NotificationType, Notification
logger = get_module_logger("observation_info") logger = get_module_logger("pfc_observation_info")
TIME_ZONE = tz.gettz(global_config.TIME_ZONE if global_config else "Asia/Shanghai") # 使用配置的时区,提供默认值 TIME_ZONE = tz.gettz(global_config.TIME_ZONE if global_config else "Asia/Shanghai") # 使用配置的时区,提供默认值

View File

@ -3,7 +3,7 @@ from typing import List, Dict, Any
from src.plugins.PFC.chat_observer import ChatObserver from src.plugins.PFC.chat_observer import ChatObserver
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.plugins.models.utils_model import LLMRequest from src.plugins.models.utils_model import LLMRequest
from src.plugins.moods.moods import MoodManager # MoodManager 本身是单例 from src.manager.mood_manager import mood_manager
from src.plugins.utils.chat_message_builder import build_readable_messages from src.plugins.utils.chat_message_builder import build_readable_messages
from src.plugins.PFC.observation_info import ObservationInfo from src.plugins.PFC.observation_info import ObservationInfo
from src.plugins.PFC.conversation_info import ConversationInfo from src.plugins.PFC.conversation_info import ConversationInfo
@ -19,7 +19,7 @@ class PfcEmotionUpdater:
""" """
self.private_name = private_name self.private_name = private_name
self.bot_name = bot_name self.bot_name = bot_name
self.mood_mng = MoodManager.get_instance() # 获取 MoodManager 单例 self.mood_mng = mood_manager
# LLM 实例 (根据 global_config.llm_summary 配置) # LLM 实例 (根据 global_config.llm_summary 配置)
llm_config_summary = getattr(global_config, "llm_summary", None) llm_config_summary = getattr(global_config, "llm_summary", None)
@ -51,7 +51,7 @@ class PfcEmotionUpdater:
logger.error(f"[私聊][{self.private_name}] LLM未初始化无法进行情绪更新。") logger.error(f"[私聊][{self.private_name}] LLM未初始化无法进行情绪更新。")
# 即使LLM失败也应该更新conversation_info中的情绪文本为MoodManager的当前状态 # 即使LLM失败也应该更新conversation_info中的情绪文本为MoodManager的当前状态
if conversation_info and self.mood_mng: if conversation_info and self.mood_mng:
conversation_info.current_emotion_text = self.mood_mng.get_prompt() conversation_info.current_emotion_text = self.mood_mng.get_mood_prompt()
return return
if not self.mood_mng or not conversation_info or not observation_info: if not self.mood_mng or not conversation_info or not observation_info:
@ -97,7 +97,7 @@ class PfcEmotionUpdater:
if ( if (
detected_emotion_word detected_emotion_word
and detected_emotion_word != "无变化" and detected_emotion_word != "无变化"
and detected_emotion_word in self.mood_mng.emotion_map and detected_emotion_word in self.mood_mng.EMOTION_FACTOR_MAP
): ):
self.mood_mng.update_mood_from_emotion(detected_emotion_word, intensity=self.EMOTION_UPDATE_INTENSITY) self.mood_mng.update_mood_from_emotion(detected_emotion_word, intensity=self.EMOTION_UPDATE_INTENSITY)
logger.debug( logger.debug(
@ -114,4 +114,4 @@ class PfcEmotionUpdater:
# 无论LLM判断如何都更新conversation_info中的情绪文本以供Prompt使用 # 无论LLM判断如何都更新conversation_info中的情绪文本以供Prompt使用
if conversation_info and self.mood_mng: # 确保conversation_info有效 if conversation_info and self.mood_mng: # 确保conversation_info有效
conversation_info.current_emotion_text = self.mood_mng.get_prompt() conversation_info.current_emotion_text = self.mood_mng.get_mood_prompt()

View File

@ -1,3 +1,4 @@
# TODO: 人格侧写(不要把人格侧写的功能实现写到这里!新建文件去)
import traceback import traceback
import re import re
from typing import Any from typing import Any

View File

@ -669,7 +669,7 @@ async def build_chat_history_text(observation_info: ObservationInfo, private_nam
if hasattr(observation_info, "chat_history_str") and observation_info.chat_history_str: if hasattr(observation_info, "chat_history_str") and observation_info.chat_history_str:
chat_history_text = observation_info.chat_history_str chat_history_text = observation_info.chat_history_str
elif hasattr(observation_info, "chat_history") and observation_info.chat_history: elif hasattr(observation_info, "chat_history") and observation_info.chat_history:
history_slice = observation_info.chat_history[-20:] history_slice = observation_info.chat_history[-global_config.pfc_recent_history_display_count :]
chat_history_text = await build_readable_messages( chat_history_text = await build_readable_messages(
history_slice, replace_bot_name=True, merge_messages=False, timestamp_mode="relative", read_mark=0.0 history_slice, replace_bot_name=True, merge_messages=False, timestamp_mode="relative", read_mark=0.0
) )

View File

@ -4,7 +4,7 @@ from src.config.config import global_config # 为了获取 BOT_QQ
from .chat_observer import ChatObserver from .chat_observer import ChatObserver
import re import re
logger = get_module_logger("reply_checker") logger = get_module_logger("pfc_checker")
class ReplyChecker: class ReplyChecker:

View File

@ -12,7 +12,7 @@ from .observation_info import ObservationInfo
from .conversation_info import ConversationInfo from .conversation_info import ConversationInfo
from .pfc_utils import build_chat_history_text from .pfc_utils import build_chat_history_text
logger = get_logger("reply_generator") logger = get_logger("pfc_reply")
PROMPT_GER_VARIATIONS = [ PROMPT_GER_VARIATIONS = [
("不用输出或提及对方的网名或绰号", 0.50), ("不用输出或提及对方的网名或绰号", 0.50),

View File

@ -5,7 +5,7 @@ from ...config.config import global_config
import time import time
import asyncio import asyncio
logger = get_module_logger("waiter") logger = get_module_logger("pfc_waiter")
# --- 在这里设定你想要的超时时间(秒) --- # --- 在这里设定你想要的超时时间(秒) ---
# 例如: 120 秒 = 2 分钟 # 例如: 120 秒 = 2 分钟

View File

@ -6,7 +6,6 @@ MaiMBot插件系统
from .chat.chat_stream import chat_manager from .chat.chat_stream import chat_manager
from .emoji_system.emoji_manager import emoji_manager from .emoji_system.emoji_manager import emoji_manager
from .person_info.relationship_manager import relationship_manager from .person_info.relationship_manager import relationship_manager
from .moods.moods import MoodManager
from .willing.willing_manager import willing_manager from .willing.willing_manager import willing_manager
from .schedule.schedule_generator import bot_schedule from .schedule.schedule_generator import bot_schedule
@ -15,7 +14,6 @@ __all__ = [
"chat_manager", "chat_manager",
"emoji_manager", "emoji_manager",
"relationship_manager", "relationship_manager",
"MoodManager",
"willing_manager", "willing_manager",
"bot_schedule", "bot_schedule",
] ]

View File

@ -1,14 +1,13 @@
import traceback
from typing import Dict, Any from typing import Dict, Any
from ..moods.moods import MoodManager # 导入情绪管理器
from ...config.config import global_config
from .message import MessageRecv
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.manager.mood_manager import mood_manager # 导入情绪管理器
from .message import MessageRecv
from ..heartFC_chat.heartflow_processor import HeartFCProcessor from ..heartFC_chat.heartflow_processor import HeartFCProcessor
from ..PFC.pfc_processor import PFCProcessor from ..PFC.pfc_processor import PFCProcessor
from ..utils.prompt_builder import Prompt, global_prompt_manager from ..utils.prompt_builder import Prompt, global_prompt_manager
import traceback from ...config.config import global_config
# 定义日志配置 # 定义日志配置
@ -21,7 +20,7 @@ class ChatBot:
def __init__(self): def __init__(self):
self.bot = None # bot 实例引用 self.bot = None # bot 实例引用
self._started = False self._started = False
self.mood_manager = MoodManager.get_instance() # 获取情绪管理器单例 self.mood_manager = mood_manager # 获取情绪管理器单例
self.heartflow_processor = HeartFCProcessor() # 新增 self.heartflow_processor = HeartFCProcessor() # 新增
self.pfc_processor = PFCProcessor() self.pfc_processor = PFCProcessor()
@ -67,12 +66,14 @@ class ChatBot:
logger.debug(f"用户{userinfo.user_id}被禁止回复") logger.debug(f"用户{userinfo.user_id}被禁止回复")
return return
if groupinfo is None: if groupinfo is None and global_config.enable_friend_whitelist:
logger.trace("检测到私聊消息,检查") logger.trace("检测到私聊消息,检查")
# 好友黑名单拦截 # 好友黑名单拦截
if userinfo.user_id not in global_config.talk_allowed_private: if userinfo.user_id not in global_config.talk_allowed_private:
logger.debug(f"用户{userinfo.user_id}没有私聊权限") logger.debug(f"用户{userinfo.user_id}没有私聊权限")
return return
elif not global_config.enable_friend_whitelist:
logger.debug("私聊白名单模式未启用,跳过私聊权限检查。")
# 群聊黑名单拦截 # 群聊黑名单拦截
if groupinfo is not None and groupinfo.group_id not in global_config.talk_allowed_groups: if groupinfo is not None and groupinfo.group_id not in global_config.talk_allowed_groups:

View File

@ -1,3 +1,4 @@
# TODO: 原生多模态支持
import time import time
from abc import abstractmethod from abc import abstractmethod
from dataclasses import dataclass from dataclasses import dataclass

View File

@ -1,21 +1,20 @@
import random import random
import time
import re import re
import time
from collections import Counter from collections import Counter
import jieba import jieba
import numpy as np import numpy as np
from src.common.logger import get_module_logger from maim_message import UserInfo
from pymongo.errors import PyMongoError from pymongo.errors import PyMongoError
from src.common.logger import get_module_logger
from src.manager.mood_manager import mood_manager
from .message import MessageRecv
from ..models.utils_model import LLMRequest from ..models.utils_model import LLMRequest
from ..utils.typo_generator import ChineseTypoGenerator from ..utils.typo_generator import ChineseTypoGenerator
from ...config.config import global_config
from .message import MessageRecv
from maim_message import UserInfo
from ..moods.moods import MoodManager
from ...common.database import db from ...common.database import db
from ...config.config import global_config
logger = get_module_logger("chat_utils") logger = get_module_logger("chat_utils")
@ -252,7 +251,7 @@ def split_into_sentences_w_remove_punctuation(text: str) -> list[str]:
if len_text < 12: if len_text < 12:
split_strength = 0.2 split_strength = 0.2
elif len_text < 32: elif len_text < 32:
split_strength = 0.6 split_strength = 0.5
else: else:
split_strength = 0.7 split_strength = 0.7
# 合并概率与分割强度相反 # 合并概率与分割强度相反
@ -371,7 +370,7 @@ def process_llm_response(text: str) -> list[str]:
else: else:
sentences.append(sentence) sentences.append(sentence)
if len(sentences) > max_sentence_num: if len(sentences) > (max_sentence_num * 2):
logger.warning(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复") logger.warning(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复")
return [f"{global_config.BOT_NICKNAME}不知道哦"] return [f"{global_config.BOT_NICKNAME}不知道哦"]
@ -405,7 +404,6 @@ def calculate_typing_time(
- 在所有输入结束后额外加上回车时间0.3 - 在所有输入结束后额外加上回车时间0.3
- 如果is_emoji为True将使用固定1秒的输入时间 - 如果is_emoji为True将使用固定1秒的输入时间
""" """
mood_manager = MoodManager.get_instance()
# 将0-1的唤醒度映射到-1到1 # 将0-1的唤醒度映射到-1到1
mood_arousal = mood_manager.current_mood.arousal mood_arousal = mood_manager.current_mood.arousal
# 映射到0.5到2倍的速度系数 # 映射到0.5到2倍的速度系数
@ -419,8 +417,8 @@ def calculate_typing_time(
if chinese_chars == 1 and len(input_string.strip()) == 1: if chinese_chars == 1 and len(input_string.strip()) == 1:
return chinese_time * 3 + 0.3 # 加上回车时间 return chinese_time * 3 + 0.3 # 加上回车时间
total_time = 0
# 正常计算所有字符的输入时间 # 正常计算所有字符的输入时间
total_time = 0.0
for char in input_string: for char in input_string:
if "\u4e00" <= char <= "\u9fff": # 判断是否为中文字符 if "\u4e00" <= char <= "\u9fff": # 判断是否为中文字符
total_time += chinese_time total_time += chinese_time
@ -617,7 +615,7 @@ def translate_timestamp_to_human_readable(timestamp: float, mode: str = "normal"
str: 格式化后的时间字符串 str: 格式化后的时间字符串
""" """
if mode == "normal": if mode == "normal":
return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) return time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(timestamp))
elif mode == "relative": elif mode == "relative":
now = time.time() now = time.time()
diff = now - timestamp diff = now - timestamp

View File

@ -144,7 +144,7 @@ class NicknameManager:
self.nickname_queue: asyncio.Queue = asyncio.Queue(maxsize=self.queue_max_size) self.nickname_queue: asyncio.Queue = asyncio.Queue(maxsize=self.queue_max_size)
self._stop_event = threading.Event() # stop_event 仍然使用 threading.Event因为它是由另一个线程设置的 self._stop_event = threading.Event() # stop_event 仍然使用 threading.Event因为它是由另一个线程设置的
self._nickname_thread: Optional[threading.Thread] = None self._nickname_thread: Optional[threading.Thread] = None
self.sleep_interval = getattr(self.config, "nickname_process_sleep_interval", 60) # 超时时间 self.sleep_interval = getattr(self.config, "nickname_process_sleep_interval", 5) # 超时时间
self._initialized = True self._initialized = True
logger.info("NicknameManager 初始化完成。") logger.info("NicknameManager 初始化完成。")
@ -177,6 +177,7 @@ class NicknameManager:
self._stop_event.set() # 设置停止事件_processing_loop 会检测到 self._stop_event.set() # 设置停止事件_processing_loop 会检测到
try: try:
# 不需要清空 asyncio.Queue让循环自然结束或被取消 # 不需要清空 asyncio.Queue让循环自然结束或被取消
# self.empty_queue(self.nickname_queue)
self._nickname_thread.join(timeout=10) # 等待线程结束 self._nickname_thread.join(timeout=10) # 等待线程结束
if self._nickname_thread.is_alive(): if self._nickname_thread.is_alive():
logger.warning("绰号处理器线程在超时后仍未停止。") logger.warning("绰号处理器线程在超时后仍未停止。")
@ -189,6 +190,13 @@ class NicknameManager:
else: else:
logger.info("绰号处理器线程未在运行或已被清理。") logger.info("绰号处理器线程未在运行或已被清理。")
# def empty_queue(self, q: asyncio.Queue):
# while not q.empty():
# # Depending on your program, you may want to
# # catch QueueEmpty
# q.get_nowait()
# q.task_done()
async def trigger_nickname_analysis( async def trigger_nickname_analysis(
self, self,
anchor_message: MessageRecv, anchor_message: MessageRecv,
@ -202,7 +210,7 @@ class NicknameManager:
if not self.is_enabled: if not self.is_enabled:
return return
if random.random() > 0.9: if random.random() < global_config.nickname_analysis_probability:
logger.debug("跳过绰号分析:随机概率未命中。") logger.debug("跳过绰号分析:随机概率未命中。")
return return
@ -358,7 +366,6 @@ class NicknameManager:
logger.info(f"{log_prefix} LLM 找到绰号映射,准备更新数据库: {nickname_map_to_update}") logger.info(f"{log_prefix} LLM 找到绰号映射,准备更新数据库: {nickname_map_to_update}")
for user_id_str, nickname in nickname_map_to_update.items(): for user_id_str, nickname in nickname_map_to_update.items():
# ... (验证和数据库更新逻辑保持不变) ...
if not user_id_str or not nickname: if not user_id_str or not nickname:
logger.warning(f"{log_prefix} 跳过无效条目: user_id='{user_id_str}', nickname='{nickname}'") logger.warning(f"{log_prefix} 跳过无效条目: user_id='{user_id_str}', nickname='{nickname}'")
continue continue
@ -394,7 +401,6 @@ class NicknameManager:
""" """
内部方法调用 LLM 分析聊天记录和 Bot 回复提取可靠的 用户ID-绰号 映射 内部方法调用 LLM 分析聊天记录和 Bot 回复提取可靠的 用户ID-绰号 映射
""" """
# ... (此方法内部逻辑保持不变) ...
if not self.llm_mapper: if not self.llm_mapper:
logger.error("LLM 映射器未初始化,无法执行分析。") logger.error("LLM 映射器未初始化,无法执行分析。")
return {"is_exist": False} return {"is_exist": False}

View File

@ -1,34 +1,36 @@
import asyncio import asyncio
import contextlib
import json # <--- 确保导入 json
import random # <--- 添加导入
import time import time
import re import re
import traceback import traceback
import random
import json
from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
from collections import deque from collections import deque
from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending from typing import List, Optional, Dict, Any, Deque, Callable, Coroutine
from src.plugins.chat.message import Seg
from src.plugins.chat.chat_stream import ChatStream
from src.plugins.chat.message import UserInfo
from src.plugins.chat.chat_stream import chat_manager
from src.common.logger_manager import get_logger
from src.plugins.models.utils_model import LLMRequest
from src.config.config import global_config
from src.plugins.chat.utils_image import image_path_to_base64
from src.plugins.utils.timer_calculator import Timer
from src.plugins.emoji_system.emoji_manager import emoji_manager
from src.heart_flow.sub_mind import SubMind
from src.heart_flow.observation import Observation
from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager, prompt_builder
import contextlib
from src.plugins.utils.chat_message_builder import num_new_messages_since
from src.plugins.heartFC_chat.heartFC_Cycleinfo import CycleInfo
from .heartFC_sender import HeartFCSender
from src.plugins.chat.utils import process_llm_response
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.moods.moods import MoodManager
from src.heart_flow.utils_chat import get_chat_type_and_target_info
from rich.traceback import install from rich.traceback import install
from src.common.logger_manager import get_logger
from src.config.config import global_config
from src.heart_flow.observation import Observation
from src.heart_flow.sub_mind import SubMind
from src.heart_flow.utils_chat import get_chat_type_and_target_info
from src.manager.mood_manager import mood_manager
from src.plugins.chat.chat_stream import ChatStream
from src.plugins.chat.chat_stream import chat_manager
from src.plugins.chat.message import MessageRecv, BaseMessageInfo, MessageThinking, MessageSending
from src.plugins.chat.message import Seg # Local import needed after move
from src.plugins.chat.message import UserInfo
from src.plugins.chat.utils import process_llm_response
from src.plugins.chat.utils_image import image_path_to_base64 # Local import needed after move
from src.plugins.emoji_system.emoji_manager import emoji_manager
from src.plugins.heartFC_chat.heartFC_Cycleinfo import CycleInfo
from src.plugins.heartFC_chat.heartflow_prompt_builder import global_prompt_manager, prompt_builder
from src.plugins.models.utils_model import LLMRequest
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.utils.chat_message_builder import num_new_messages_since
from src.plugins.utils.timer_calculator import Timer # <--- Import Timer
from .heartFC_sender import HeartFCSender
from src.plugins.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat from src.plugins.utils.chat_message_builder import get_raw_msg_before_timestamp_with_chat
from src.plugins.group_nickname.nickname_manager import nickname_manager from src.plugins.group_nickname.nickname_manager import nickname_manager
@ -1312,7 +1314,7 @@ class HeartFChatting:
""" """
try: try:
# 1. 获取情绪影响因子并调整模型温度 # 1. 获取情绪影响因子并调整模型温度
arousal_multiplier = MoodManager.get_instance().get_arousal_multiplier() arousal_multiplier = mood_manager.get_arousal_multiplier()
current_temp = global_config.llm_normal["temp"] * arousal_multiplier current_temp = global_config.llm_normal["temp"] * arousal_multiplier
self.model_normal.temperature = current_temp # 动态调整温度 self.model_normal.temperature = current_temp # 动态调整温度

View File

@ -10,7 +10,7 @@ from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.chat.utils import get_embedding from src.plugins.chat.utils import get_embedding
from ...common.database import db from ...common.database import db
from ..chat.utils import get_recent_group_speaker from ..chat.utils import get_recent_group_speaker
from ..moods.moods import MoodManager from src.manager.mood_manager import mood_manager
from ..memory_system.Hippocampus import HippocampusManager from ..memory_system.Hippocampus import HippocampusManager
from ..schedule.schedule_generator import bot_schedule from ..schedule.schedule_generator import bot_schedule
from ..knowledge.knowledge_lib import qa_manager from ..knowledge.knowledge_lib import qa_manager
@ -392,8 +392,7 @@ class PromptBuilder:
else: else:
logger.warning(f"Invalid person tuple encountered for relationship prompt: {person}") logger.warning(f"Invalid person tuple encountered for relationship prompt: {person}")
mood_manager = MoodManager.get_instance() mood_prompt = mood_manager.get_mood_prompt()
mood_prompt = mood_manager.get_prompt()
reply_styles1 = [ reply_styles1 = [
("给出日常且口语化的回复,平淡一些", 0.30), ("给出日常且口语化的回复,平淡一些", 0.30),
("给出非常简短的回复", 0.30), ("给出非常简短的回复", 0.30),

View File

@ -1,25 +1,26 @@
import time
import asyncio import asyncio
import traceback
import statistics # 导入 statistics 模块 import statistics # 导入 statistics 模块
import time
import traceback
from random import random from random import random
from typing import List, Optional # 导入 Optional from typing import List, Optional # 导入 Optional
from ..moods.moods import MoodManager
from ...config.config import global_config
from ..emoji_system.emoji_manager import emoji_manager
from .normal_chat_generator import NormalChatGenerator
from ..chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet
from ..chat.message_sender import message_manager
from ..chat.utils_image import image_path_to_base64
from ..willing.willing_manager import willing_manager
from maim_message import UserInfo, Seg from maim_message import UserInfo, Seg
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.heart_flow.utils_chat import get_chat_type_and_target_info
from src.manager.mood_manager import mood_manager
from src.plugins.chat.chat_stream import ChatStream, chat_manager from src.plugins.chat.chat_stream import ChatStream, chat_manager
from src.plugins.person_info.relationship_manager import relationship_manager from src.plugins.person_info.relationship_manager import relationship_manager
from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager from src.plugins.respon_info_catcher.info_catcher import info_catcher_manager
from src.plugins.utils.timer_calculator import Timer from src.plugins.utils.timer_calculator import Timer
from src.heart_flow.utils_chat import get_chat_type_and_target_info from .normal_chat_generator import NormalChatGenerator
from ..chat.message import MessageSending, MessageRecv, MessageThinking, MessageSet
from ..chat.message_sender import message_manager
from ..chat.utils_image import image_path_to_base64
from ..emoji_system.emoji_manager import emoji_manager
from ..willing.willing_manager import willing_manager
from ...config.config import global_config
from src.plugins.group_nickname.nickname_manager import nickname_manager from src.plugins.group_nickname.nickname_manager import nickname_manager
@ -46,7 +47,7 @@ class NormalChat:
# Other sync initializations # Other sync initializations
self.gpt = NormalChatGenerator() self.gpt = NormalChatGenerator()
self.mood_manager = MoodManager.get_instance() self.mood_manager = mood_manager
self.start_time = time.time() self.start_time = time.time()
self.last_speak_time = 0 self.last_speak_time = 0
self._chat_task: Optional[asyncio.Task] = None self._chat_task: Optional[asyncio.Task] = None

View File

@ -1,293 +0,0 @@
import math
import threading
import time
from dataclasses import dataclass
from ...config.config import global_config
from src.common.logger_manager import get_logger
from ..person_info.relationship_manager import relationship_manager
from src.individuality.individuality import Individuality
logger = get_logger("mood")
@dataclass
class MoodState:
valence: float # 愉悦度 (-1.0 到 1.0)-1表示极度负面1表示极度正面
arousal: float # 唤醒度 (-1.0 到 1.0)-1表示抑制1表示兴奋
text: str # 心情文本描述
class MoodManager:
_instance = None
_lock = threading.Lock()
def __new__(cls):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
# 确保初始化代码只运行一次
if self._initialized:
return
self._initialized = True
# 初始化心情状态
self.current_mood = MoodState(valence=0.0, arousal=0.0, text="平静")
# 从配置文件获取衰减率
self.decay_rate_valence = 1 - global_config.mood_decay_rate # 愉悦度衰减率
self.decay_rate_arousal = 1 - global_config.mood_decay_rate # 唤醒度衰减率
# 上次更新时间
self.last_update = time.time()
# 线程控制
self._running = False
self._update_thread = None
# 情绪词映射表 (valence, arousal)
self.emotion_map = {
"开心": (0.21, 0.6),
"害羞": (0.15, 0.2),
"愤怒": (-0.24, 0.8),
"恐惧": (-0.21, 0.7),
"悲伤": (-0.21, 0.3),
"厌恶": (-0.12, 0.4),
"惊讶": (0.06, 0.7),
"困惑": (0.0, 0.6),
"平静": (0.03, 0.5),
}
# 情绪文本映射表
self.mood_text_map = {
# 第一象限:高唤醒,正愉悦
(0.5, 0.4): "兴奋",
(0.3, 0.6): "快乐",
(0.2, 0.3): "满足",
# 第二象限:高唤醒,负愉悦
(-0.5, 0.4): "愤怒",
(-0.3, 0.6): "焦虑",
(-0.2, 0.3): "烦躁",
# 第三象限:低唤醒,负愉悦
(-0.5, -0.4): "悲伤",
(-0.3, -0.3): "疲倦",
(-0.4, -0.7): "疲倦",
# 第四象限:低唤醒,正愉悦
(0.2, -0.1): "平静",
(0.3, -0.2): "安宁",
(0.5, -0.4): "放松",
}
@classmethod
def get_instance(cls) -> "MoodManager":
"""获取MoodManager的单例实例"""
if cls._instance is None:
cls._instance = MoodManager()
return cls._instance
def start_mood_update(self, update_interval: float = 5.0) -> None:
"""
启动情绪更新线程
:param update_interval: 更新间隔
"""
if self._running:
return
self._running = True
self._update_thread = threading.Thread(
target=self._continuous_mood_update, args=(update_interval,), daemon=True
)
self._update_thread.start()
def stop_mood_update(self) -> None:
"""停止情绪更新线程"""
self._running = False
if self._update_thread and self._update_thread.is_alive():
self._update_thread.join()
def _continuous_mood_update(self, update_interval: float) -> None:
"""
持续更新情绪状态的线程函数
:param update_interval: 更新间隔
"""
while self._running:
self._apply_decay()
self._update_mood_text()
time.sleep(update_interval)
def _apply_decay(self) -> None:
"""应用情绪衰减,正向和负向情绪分开计算"""
current_time = time.time()
time_diff = current_time - self.last_update
agreeableness_factor = 1
agreeableness_bias = 0
neuroticism_factor = 0.5
# 获取人格特质
personality = Individuality.get_instance().personality
if personality:
# 神经质:影响情绪变化速度
neuroticism_factor = 1 + (personality.neuroticism - 0.5) * 0.4
agreeableness_factor = 1 + (personality.agreeableness - 0.5) * 0.4
# 宜人性:影响情绪基准线
if personality.agreeableness < 0.2:
agreeableness_bias = (personality.agreeableness - 0.2) * 0.5
elif personality.agreeableness > 0.8:
agreeableness_bias = (personality.agreeableness - 0.8) * 0.5
else:
agreeableness_bias = 0
# 分别计算正向和负向的衰减率
if self.current_mood.valence >= 0:
# 正向情绪衰减
decay_rate_positive = self.decay_rate_valence * (1 / agreeableness_factor)
valence_target = 0 + agreeableness_bias
self.current_mood.valence = valence_target + (self.current_mood.valence - valence_target) * math.exp(
-decay_rate_positive * time_diff * neuroticism_factor
)
else:
# 负向情绪衰减
decay_rate_negative = self.decay_rate_valence * agreeableness_factor
valence_target = 0 + agreeableness_bias
self.current_mood.valence = valence_target + (self.current_mood.valence - valence_target) * math.exp(
-decay_rate_negative * time_diff * neuroticism_factor
)
# Arousal 向中性0回归
arousal_target = 0
self.current_mood.arousal = arousal_target + (self.current_mood.arousal - arousal_target) * math.exp(
-self.decay_rate_arousal * time_diff * neuroticism_factor
)
# 确保值在合理范围内
self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence))
self.current_mood.arousal = max(-1.0, min(1.0, self.current_mood.arousal))
self.last_update = current_time
def update_mood_from_text(self, text: str, valence_change: float, arousal_change: float) -> None:
"""根据输入文本更新情绪状态"""
self.current_mood.valence += valence_change
self.current_mood.arousal += arousal_change
# 限制范围
self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence))
self.current_mood.arousal = max(-1.0, min(1.0, self.current_mood.arousal))
self._update_mood_text()
def set_mood_text(self, text: str) -> None:
"""直接设置心情文本"""
self.current_mood.text = text
def _update_mood_text(self) -> None:
"""根据当前情绪状态更新文本描述"""
closest_mood = None
min_distance = float("inf")
for (v, a), text in self.mood_text_map.items():
distance = math.sqrt((self.current_mood.valence - v) ** 2 + (self.current_mood.arousal - a) ** 2)
if distance < min_distance:
min_distance = distance
closest_mood = text
if closest_mood:
self.current_mood.text = closest_mood
def update_mood_by_user(self, user_id: str, valence_change: float, arousal_change: float) -> None:
"""根据用户ID更新情绪状态"""
# 这里可以根据用户ID添加特定的权重或规则
weight = 1.0 # 默认权重
self.current_mood.valence += valence_change * weight
self.current_mood.arousal += arousal_change * weight
# 限制范围
self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence))
self.current_mood.arousal = max(-1.0, min(1.0, self.current_mood.arousal))
self._update_mood_text()
def get_prompt(self) -> str:
"""根据当前情绪状态生成提示词"""
base_prompt = f"当前心情:{self.current_mood.text}"
# 根据情绪状态添加额外的提示信息
if self.current_mood.valence > 0.5:
base_prompt += "你现在心情很好,"
elif self.current_mood.valence < -0.5:
base_prompt += "你现在心情不太好,"
if self.current_mood.arousal > 0.4:
base_prompt += "情绪比较激动。"
elif self.current_mood.arousal < -0.4:
base_prompt += "情绪比较平静。"
return base_prompt
def get_arousal_multiplier(self) -> float:
"""根据当前情绪状态返回唤醒度乘数"""
if self.current_mood.arousal > 0.4:
multiplier = 1 + min(0.15, (self.current_mood.arousal - 0.4) / 3)
return multiplier
elif self.current_mood.arousal < -0.4:
multiplier = 1 - min(0.15, ((0 - self.current_mood.arousal) - 0.4) / 3)
return multiplier
return 1.0
def get_current_mood(self) -> MoodState:
"""获取当前情绪状态"""
return self.current_mood
def print_mood_status(self) -> None:
"""打印当前情绪状态"""
logger.info(
f"愉悦度: {self.current_mood.valence:.2f}, "
f"唤醒度: {self.current_mood.arousal:.2f}, "
f"心情: {self.current_mood.text}"
)
def update_mood_from_emotion(self, emotion: str, intensity: float = 1.0) -> None:
"""
根据情绪词更新心情状态
:param emotion: 情绪词'happy', 'sad'
:param intensity: 情绪强度0.0-1.0
"""
if emotion not in self.emotion_map:
logger.debug(f"[情绪更新] 未知情绪词: {emotion}")
return
valence_change, arousal_change = self.emotion_map[emotion]
old_valence = self.current_mood.valence
old_arousal = self.current_mood.arousal
old_mood = self.current_mood.text
valence_change = relationship_manager.feedback_to_mood(valence_change)
# 应用情绪强度
valence_change *= intensity
arousal_change *= intensity
# 更新当前情绪状态
self.current_mood.valence += valence_change
self.current_mood.arousal += arousal_change
# 限制范围
self.current_mood.valence = max(-1.0, min(1.0, self.current_mood.valence))
self.current_mood.arousal = max(-1.0, min(1.0, self.current_mood.arousal))
self._update_mood_text()
logger.info(
f"[情绪变化] {emotion}(强度:{intensity:.2f}) | 愉悦度:{old_valence:.2f}->{self.current_mood.valence:.2f}, 唤醒度:{old_arousal:.2f}->{self.current_mood.arousal:.2f} | 心情:{old_mood}->{self.current_mood.text}"
)

View File

@ -8,6 +8,9 @@ import random
from typing import List, Dict from typing import List, Dict
from ...common.database import db from ...common.database import db
from maim_message import UserInfo from maim_message import UserInfo
from ...manager.mood_manager import mood_manager
# import re # import re
# import traceback # import traceback
@ -24,9 +27,7 @@ class RelationshipManager:
@property @property
def mood_manager(self): def mood_manager(self):
if self._mood_manager is None: if self._mood_manager is None:
from ..moods.moods import MoodManager # 延迟导入 self._mood_manager = mood_manager
self._mood_manager = MoodManager.get_instance()
return self._mood_manager return self._mood_manager
def positive_feedback_sys(self, label: str, stance: str): def positive_feedback_sys(self, label: str, stance: str):
@ -62,9 +63,7 @@ class RelationshipManager:
def mood_feedback(self, value): def mood_feedback(self, value):
"""情绪反馈""" """情绪反馈"""
mood_manager = self.mood_manager mood_manager = self.mood_manager
mood_gain = mood_manager.get_current_mood().valence ** 2 * math.copysign( mood_gain = mood_manager.current_mood.valence**2 * math.copysign(1, value * mood_manager.current_mood.valence)
1, value * mood_manager.get_current_mood().valence
)
value += value * mood_gain value += value * mood_gain
logger.info(f"当前relationship增益系数{mood_gain:.3f}") logger.info(f"当前relationship增益系数{mood_gain:.3f}")
return value return value

View File

@ -1,4 +0,0 @@
from .remote import main
# 启动心跳线程
heartbeat_thread = main()

View File

@ -1,248 +1,142 @@
import asyncio
import requests import requests
import time
import uuid
import platform import platform
import os
import json
import threading
import subprocess
# from loguru import logger # from loguru import logger
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.config.config import global_config from src.config.config import global_config
from src.manager.async_task_manager import AsyncTask
from src.manager.local_store_manager import local_storage
logger = get_logger("remote") logger = get_logger("remote")
# --- 使用向上导航的方式定义路径 --- TELEMETRY_SERVER_URL = "http://localhost:8080"
"""遥测服务地址"""
# 1. 获取当前文件 (remote.py) 所在的目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 2. 从当前目录向上导航三级找到项目根目录
# (src/plugins/remote/ -> src/plugins/ -> src/ -> project_root)
root_dir = os.path.abspath(os.path.join(current_dir, "..", "..", ".."))
# 3. 定义 data 目录的路径 (位于项目根目录下)
data_dir = os.path.join(root_dir, "data")
# 4. 定义 UUID 文件在 data 目录下的完整路径
UUID_FILE = os.path.join(data_dir, "client_uuid.json")
# --- 路径定义结束 ---
# 生成或获取客户端唯一ID class TelemetryHeartBeatTask(AsyncTask):
def get_unique_id(): HEARTBEAT_INTERVAL = 300
# --- 在尝试读写 UUID_FILE 之前确保 data 目录存在 ---
# 将目录检查和创建逻辑移到这里,在首次需要写入前执行
try:
# exist_ok=True 意味着如果目录已存在也不会报错
os.makedirs(data_dir, exist_ok=True)
except OSError as e:
# 处理可能的权限错误等
logger.error(f"无法创建数据目录 {data_dir}: {e}")
# 根据你的错误处理逻辑,可能需要在这里返回错误或抛出异常
# 暂且返回 None 或抛出,避免继续执行导致问题
raise RuntimeError(f"无法创建必要的数据目录 {data_dir}") from e
# --- 目录检查结束 ---
# 检查是否已经有保存的UUID def __init__(self):
if os.path.exists(UUID_FILE): super().__init__(task_name="Telemetry Heart Beat Task", run_interval=self.HEARTBEAT_INTERVAL)
try: self.server_url = TELEMETRY_SERVER_URL
with open(UUID_FILE, "r", encoding="utf-8") as f: # 指定 encoding """遥测服务地址"""
data = json.load(f)
if "client_id" in data:
logger.debug(f"从本地文件读取客户端ID: {UUID_FILE}")
return data["client_id"]
except (json.JSONDecodeError, IOError) as e:
logger.warning(f"读取UUID文件 {UUID_FILE} 出错: {e}将生成新的UUID")
except Exception as e: # 捕捉其他可能的异常
logger.error(f"读取UUID文件 {UUID_FILE} 时发生未知错误: {e}")
# 如果没有保存的UUID或读取出错则生成新的 self.client_uuid = local_storage["mmc_uuid"] if "mmc_uuid" in local_storage else None
client_id = generate_unique_id() """客户端UUID"""
logger.info(f"生成新的客户端ID: {client_id}")
# 保存UUID到文件 self.info_dict = self._get_sys_info()
try: """系统信息字典"""
# 再次确认目录存在 (虽然理论上前面已创建,但更保险)
os.makedirs(data_dir, exist_ok=True)
with open(UUID_FILE, "w", encoding="utf-8") as f: # 指定 encoding
json.dump({"client_id": client_id}, f, indent=4) # 添加 indent 使json可读
logger.info(f"已保存新生成的客户端ID到本地文件: {UUID_FILE}")
except IOError as e:
logger.error(f"保存UUID时出错: {UUID_FILE} - {e}")
except Exception as e: # 捕捉其他可能的异常
logger.error(f"保存UUID文件 {UUID_FILE} 时发生未知错误: {e}")
return client_id @staticmethod
def _get_sys_info() -> dict[str, str]:
"""获取系统信息"""
info_dict = {
"os_type": "Unknown",
"py_version": platform.python_version(),
"mmc_version": global_config.MAI_VERSION,
}
match platform.system():
case "Windows":
info_dict["os_type"] = "Windows"
case "Linux":
info_dict["os_type"] = "Linux"
case "Darwin":
info_dict["os_type"] = "macOS"
case _:
info_dict["os_type"] = "Unknown"
# 生成客户端唯一ID return info_dict
def generate_unique_id():
# 基于机器码生成唯一ID同一台机器上生成的UUID是固定的只要机器码不变
import hashlib
system_info = platform.system() async def _req_uuid(self) -> bool:
machine_code = None """
向服务端请求UUID不应在已存在UUID的情况下调用会覆盖原有的UUID
"""
try: if "deploy_time" not in local_storage:
if system_info == "Windows": logger.error("本地存储中缺少部署时间无法请求UUID")
# 使用wmic命令获取主机UUID更稳定
result = subprocess.check_output(
"wmic csproduct get uuid", shell=True, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
)
lines = result.decode(errors="ignore").splitlines()
# 过滤掉空行和表头只取有效UUID
uuids = [line.strip() for line in lines if line.strip() and line.strip().lower() != "uuid"]
if uuids:
uuid_val = uuids[0]
# logger.debug(f"主机UUID: {uuid_val}")
# 增加无效值判断
if uuid_val and uuid_val.lower() not in ["to be filled by o.e.m.", "none", "", "standard"]:
machine_code = uuid_val
elif system_info == "Linux":
# 优先读取 /etc/machine-id其次 /var/lib/dbus/machine-id取第一个非空且内容有效的
for path in ["/etc/machine-id", "/var/lib/dbus/machine-id"]:
if os.path.exists(path):
with open(path, "r") as f:
code = f.read().strip()
# 只要内容非空且不是全0
if code and set(code) != {"0"}:
machine_code = code
break
elif system_info == "Darwin":
# macOS: 使用IOPlatformUUID
result = subprocess.check_output(
"ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/'", shell=True
)
uuid_line = result.decode(errors="ignore")
# 解析出 "IOPlatformUUID" = "xxxx-xxxx-xxxx-xxxx"
import re
m = re.search(r'"IOPlatformUUID"\s*=\s*"([^"]+)"', uuid_line)
if m:
uuid_val = m.group(1)
logger.debug(f"IOPlatformUUID: {uuid_val}")
if uuid_val and uuid_val.lower() not in ["to be filled by o.e.m.", "none", "", "standard"]:
machine_code = uuid_val
except Exception as e:
logger.debug(f"获取机器码失败: {e}")
# 如果主板序列号无效尝试用MAC地址
if not machine_code:
try:
mac = uuid.getnode()
if (mac >> 40) % 2 == 0: # 不是本地伪造MAC
machine_code = str(mac)
except Exception as e:
logger.debug(f"获取MAC地址失败: {e}")
def md5_to_uuid(md5hex):
# 将32位md5字符串格式化为8-4-4-4-12的UUID格式
return f"{md5hex[0:8]}-{md5hex[8:12]}-{md5hex[12:16]}-{md5hex[16:20]}-{md5hex[20:32]}"
if machine_code:
# print(f"machine_code={machine_code!r}") # 可用于调试
md5 = hashlib.md5(machine_code.encode("utf-8")).hexdigest()
uuid_str = md5_to_uuid(md5)
else:
uuid_str = str(uuid.uuid4())
unique_id = f"{system_info}-{uuid_str}"
return unique_id
def send_heartbeat(server_url, client_id):
"""向服务器发送心跳"""
sys = platform.system()
try:
headers = {"Client-ID": client_id, "User-Agent": f"HeartbeatClient/{client_id[:8]}"}
data = json.dumps(
{"system": sys, "Version": global_config.MAI_VERSION},
)
logger.debug(f"正在发送心跳到服务器: {server_url}")
logger.debug(f"心跳数据: {data}")
response = requests.post(f"{server_url}/api/clients", headers=headers, data=data)
if response.status_code == 201:
data = response.json()
logger.debug(f"心跳发送成功。服务器响应: {data}")
return True
else:
logger.debug(f"心跳发送失败。状态码: {response.status_code}, 响应内容: {response.text}")
return False return False
except requests.RequestException as e: try_count: int = 0
# 如果请求异常,可能是网络问题,不记录错误 while True:
logger.debug(f"发送心跳时出错: {e}") # 如果不存在则向服务端请求一个新的UUID注册客户端
return False logger.info("正在向遥测服务端请求UUID...")
try:
response = requests.post(
f"{TELEMETRY_SERVER_URL}/stat/reg_client",
json={"deploy_time": local_storage["deploy_time"]},
)
class HeartbeatThread(threading.Thread): if response.status_code == 200:
"""心跳线程类""" data = response.json()
client_id = data.get("mmc_uuid")
if client_id:
# 将UUID存储到本地
local_storage["mmc_uuid"] = client_id
self.client_uuid = client_id
logger.info(f"成功获取UUID: {self.client_uuid}")
return True # 成功获取UUID返回True
else:
logger.error("无效的服务端响应")
else:
logger.error(f"请求UUID失败状态码: {response.status_code}, 响应内容: {response.text}")
except requests.RequestException as e:
logger.error(f"请求UUID时出错: {e}") # 可能是网络问题
def __init__(self, server_url, interval): # 请求失败,重试次数+1
super().__init__(daemon=True) # 设置为守护线程,主程序结束时自动结束 try_count += 1
self.server_url = server_url if try_count > 3:
self.interval = interval # 如果超过3次仍然失败则退出
self.client_id = get_unique_id() logger.error("获取UUID失败请检查网络连接或服务端状态")
self.running = True return False
self.stop_event = threading.Event() # 添加事件对象用于可中断的等待
self.last_heartbeat_time = 0 # 记录上次发送心跳的时间
def run(self):
"""线程运行函数"""
logger.debug(f"心跳线程已启动客户端ID: {self.client_id}")
while self.running:
# 发送心跳
if send_heartbeat(self.server_url, self.client_id):
logger.info(f"{self.interval}秒后发送下一次心跳...")
else: else:
logger.info(f"{self.interval}秒后重试...") # 如果可以重试,等待后继续(指数退避)
await asyncio.sleep(4**try_count)
self.last_heartbeat_time = time.time() async def _send_heartbeat(self):
"""向服务器发送心跳"""
try:
headers = {
"Client-UUID": self.client_uuid,
"User-Agent": f"HeartbeatClient/{self.client_uuid[:8]}",
}
# 使用可中断的等待代替 sleep logger.debug(f"正在发送心跳到服务器: {self.server_url}")
# 每秒检查一次是否应该停止或发送心跳
remaining_wait = self.interval
while remaining_wait > 0 and self.running:
# 每次最多等待1秒便于及时响应停止请求
wait_time = min(1, remaining_wait)
if self.stop_event.wait(wait_time):
break # 如果事件被设置,立即退出等待
remaining_wait -= wait_time
# 检查是否由于外部原因导致间隔异常延长 response = requests.post(
if time.time() - self.last_heartbeat_time >= self.interval * 1.5: f"{self.server_url}/stat/client_heartbeat",
logger.warning("检测到心跳间隔异常延长,立即发送心跳") headers=headers,
break json=self.info_dict,
)
def stop(self): # 处理响应
"""停止线程""" if 200 <= response.status_code < 300:
self.running = False # 成功
self.stop_event.set() # 设置事件,中断等待 logger.debug(f"心跳发送成功,状态码: {response.status_code}")
logger.debug("心跳线程已收到停止信号") elif response.status_code == 403:
# 403 Forbidden
logger.error(
"心跳发送失败403 Forbidden: 可能是UUID无效或未注册。"
"处理措施重置UUID下次发送心跳时将尝试重新注册。"
)
self.client_uuid = None
del local_storage["mmc_uuid"] # 删除本地存储的UUID
else:
# 其他错误
logger.error(f"心跳发送失败,状态码: {response.status_code}, 响应内容: {response.text}")
except requests.RequestException as e:
logger.error(f"心跳发送失败: {e}")
def main(): async def run(self):
if global_config.remote_enable: # 发送心跳
"""主函数,启动心跳线程""" if global_config.remote_enable:
# 配置 if self.client_uuid is None:
server_url = "http://hyybuth.xyz:10058" if not await self._req_uuid():
# server_url = "http://localhost:10058" logger.error("获取UUID失败跳过此次心跳")
heartbeat_interval = 300 # 5分钟 return
# 创建并启动心跳线程 await self._send_heartbeat()
heartbeat_thread = HeartbeatThread(server_url, heartbeat_interval)
heartbeat_thread.start()
return heartbeat_thread # 返回线程对象,便于外部控制
return None
# --- 测试用例 ---
if __name__ == "__main__":
print("测试唯一ID生成")
print("唯一ID:", get_unique_id())

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[inner] [inner]
version = "1.6.2.3" version = "1.6.2.4"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请在修改后将version的值进行变更 #如果你想要修改配置文件请在修改后将version的值进行变更
@ -130,8 +130,9 @@ enable_nickname_mapping = false # 绰号映射功能总开关(默认关闭,
max_nicknames_in_prompt = 10 # Prompt 中最多注入的绰号数量防止token数量爆炸 max_nicknames_in_prompt = 10 # Prompt 中最多注入的绰号数量防止token数量爆炸
nickname_probability_smoothing = 1 # 绰号加权随机选择的平滑因子 nickname_probability_smoothing = 1 # 绰号加权随机选择的平滑因子
nickname_queue_max_size = 100 # 绰号处理队列最大容量 nickname_queue_max_size = 100 # 绰号处理队列最大容量
nickname_process_sleep_interval = 5 # 绰号处理进程休眠间隔(秒) nickname_process_sleep_interval = 5 # 绰号处理进程休眠间隔(秒)不建议超过5否则大概率导致结束过程中超时
nickname_analysis_history_limit = 30 # 绰号处理可见最大上下文 nickname_analysis_history_limit = 30 # 绰号处理可见最大上下文
nickname_analysis_probability = 0.1 # 绰号随机概率命中,该值越大,绰号分析越频繁
[memory] [memory]
build_memory_interval = 2000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多 build_memory_interval = 2000 # 记忆构建间隔 单位秒 间隔越低,麦麦学习越多,但是冗余信息也会增多
@ -194,19 +195,43 @@ enable_kaomoji_protection = false # 是否启用颜文字保护
model_max_output_length = 256 # 模型单次返回的最大token数 model_max_output_length = 256 # 模型单次返回的最大token数
[remote] #发送统计信息,主要是看全球有多少只麦麦 [remote] #发送统计信息,主要是看全球有多少只麦麦
enable = true enable = false
[experimental] #实验性功能 [experimental] #实验性功能
enable_friend_chat = false # 是否启用好友聊天 enable_friend_chat = true # 是否启用好友聊天
enable_friend_whitelist = true # 是否启用好友聊天白名单
talk_allowed_private = [] # 可以回复消息的QQ号 talk_allowed_private = [] # 可以回复消息的QQ号
pfc_chatting = false # 是否启用PFC聊天该功能仅作用于私聊与回复模式独立 api_polling_max_retries = 3 # 神秘小功能
api_polling_max_retries = 3
enable_pfc_reply_checker = true # 是否启用 PFC 的回复检查器
pfc_message_buffer_size = 2 # PFC 聊天消息缓冲数量,有利于使聊天节奏更加紧凑流畅,请根据实际 LLM 响应速度进行调整默认2条
rename_person = true # 是否启用改名工具,可以让麦麦对唯一名进行更改,可能可以更拟人地称呼他人,但是也可能导致记忆混淆的问题 rename_person = true # 是否启用改名工具,可以让麦麦对唯一名进行更改,可能可以更拟人地称呼他人,但是也可能导致记忆混淆的问题
[idle_chat] [pfc]
enable_idle_chat = false # 是否启用 pfc 主动发言 enable_pfc_chatting = true # 是否启用PFC聊天该功能仅作用于私聊与回复模式独立
pfc_message_buffer_size = 2 # PFC 聊天消息缓冲数量,有利于使聊天节奏更加紧凑流畅,请根据实际 LLM 响应速度进行调整默认2条
pfc_recent_history_display_count = 20 # PFC 对话最大可见上下文
[[pfc.checker]]
enable_pfc_reply_checker = true # 是否启用 PFC 的回复检查器
pfc_max_reply_attempts = 3 # 发言最多尝试次数
pfc_max_chat_history_for_checker = 50 # checker聊天记录最大可见上文长度
[[pfc.emotion]]
pfc_emotion_update_intensity = 0.6 # 情绪更新强度
pfc_emotion_history_count = 5 # 情绪更新最大可见上下文长度
[[pfc.relationship]]
pfc_relationship_incremental_interval = 10 # 关系值增值强度
pfc_relationship_incremental_msg_count = 10 # 会话中,关系值判断最大可见上下文
pfc_relationship_incremental_default_change = 1.0 # 会话中,关系值默认更新值(当 llm 返回错误时默认采用该值)
pfc_relationship_incremental_max_change = 5.0 # 会话中,关系值最大可变值
pfc_relationship_final_msg_count = 30 # 会话结束时,关系值判断最大可见上下文
pfc_relationship_final_default_change =5.0 # 会话结束时,关系值默认更新值
pfc_relationship_final_max_change = 50.0 # 会话结束时,关系值最大可变值
[[pfc.fallback]]
pfc_historical_fallback_exclude_seconds = 7200 # pfc 翻看聊天记录排除最近时长
[[pfc.idle_chat]]
enable_idle_chat = true # 是否启用 pfc 主动发言
idle_check_interval = 10 # 检查间隔10分钟检查一次 idle_check_interval = 10 # 检查间隔10分钟检查一次
min_cooldown = 7200 # 最短冷却时间2小时 (7200秒) min_cooldown = 7200 # 最短冷却时间2小时 (7200秒)
max_cooldown = 18000 # 最长冷却时间5小时 (18000秒) max_cooldown = 18000 # 最长冷却时间5小时 (18000秒)
@ -306,14 +331,6 @@ temp = 0.3
pri_in = 2 pri_in = 2
pri_out = 8 pri_out = 8
# PFC 关系评估LLM
[model.llm_PFC_relationship_eval]
name = "Pro/deepseek-ai/DeepSeek-V3" # 或者其他你认为适合判断任务的模型
provider = "SILICONFLOW"
temp = 0.4
pri_in = 2
pri_out = 8
#绰号映射生成模型 #绰号映射生成模型
[model.llm_nickname_mapping] [model.llm_nickname_mapping]
name = "Qwen/Qwen2.5-32B-Instruct" name = "Qwen/Qwen2.5-32B-Instruct"
@ -338,6 +355,16 @@ temp = 0.3
pri_in = 2 pri_in = 2
pri_out = 8 pri_out = 8
# PFC 关系评估LLM
[model.llm_PFC_relationship_eval]
name = "deepseek-ai/DeepSeek-V3"
provider = "SILICONFLOW"
temp = 0.4
max_tokens = 512
pri_in = 2
pri_out = 8
#以下模型暂时没有使用!! #以下模型暂时没有使用!!
#以下模型暂时没有使用!! #以下模型暂时没有使用!!
#以下模型暂时没有使用!! #以下模型暂时没有使用!!