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

@@ -1,8 +1,9 @@
# Changelog # Changelog
## [0.7.0] -2025-5-19 ## [0.7.0] -2025-6-1
- 重构数据库弃用MongoDB采用轻量sqlite,无需额外安装 - 重构数据库弃用MongoDB采用轻量sqlite,无需额外安装
- 重构HFC可扩展的聊天模式初步支持插件v0.1 - 重构HFC可扩展的聊天模式
- HFC初步支持插件v0.1(测试版)
- 重构表情包模块 - 重构表情包模块
- 移除日程系统 - 移除日程系统

View File

@@ -4,8 +4,8 @@ HeartFC_chat 是一个基于心流理论的聊天系统,通过模拟人类的
## 核心工作流程 ## 核心工作流程
### 1. 消息处理与存储 (HeartFCProcessor) ### 1. 消息处理与存储 (HeartFCMessageReceiver)
[代码位置: src/plugins/focus_chat/heartflow_processor.py] [代码位置: src/plugins/focus_chat/heartflow_message_receiver.py]
消息处理器负责接收和预处理消息,主要完成以下工作: 消息处理器负责接收和预处理消息,主要完成以下工作:
```mermaid ```mermaid
@@ -132,7 +132,7 @@ graph TD
### 关键参数 ### 关键参数
- LLM配置`model_normal` [heartFC_generator.py 行号: 32-37] - LLM配置`model_normal` [heartFC_generator.py 行号: 32-37]
- 过滤规则:`_check_ban_words()`, `_check_ban_regex()` [heartflow_processor.py 行号: 196-215] - 过滤规则:`_check_ban_words()`, `_check_ban_regex()` [heartflow_message_receiver.py 行号: 196-215]
- 状态控制:`INITIAL_DURATION = 60.0` [focus_chat.py 行号: 11] - 状态控制:`INITIAL_DURATION = 60.0` [focus_chat.py 行号: 11]
### 优化建议 ### 优化建议

View File

@@ -113,7 +113,7 @@ c HeartFChatting工作方式
### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow) ### 1.5. 消息处理与回复流程 (Message Processing vs. Replying Flow)
- **关注点分离**: 系统严格区分了接收和处理传入消息的流程与决定和生成回复的流程。 - **关注点分离**: 系统严格区分了接收和处理传入消息的流程与决定和生成回复的流程。
- **消息处理 (Processing)**: - **消息处理 (Processing)**:
- 由一个独立的处理器(例如 `HeartFCProcessor`)负责接收原始消息数据。 - 由一个独立的处理器(例如 `HeartFCMessageReceiver`)负责接收原始消息数据。
- 职责包括:消息解析 (`MessageRecv`)、过滤(屏蔽词、正则表达式)、基于记忆系统的初步兴趣计算 (`HippocampusManager`)、消息存储 (`MessageStorage`) 以及用户关系更新 (`RelationshipManager`)。 - 职责包括:消息解析 (`MessageRecv`)、过滤(屏蔽词、正则表达式)、基于记忆系统的初步兴趣计算 (`HippocampusManager`)、消息存储 (`MessageStorage`) 以及用户关系更新 (`RelationshipManager`)。
- 处理后的消息信息(如计算出的兴趣度)会传递给对应的 `SubHeartflow` - 处理后的消息信息(如计算出的兴趣度)会传递给对应的 `SubHeartflow`
- **回复决策与生成 (Replying)**: - **回复决策与生成 (Replying)**:
@@ -121,7 +121,7 @@ c HeartFChatting工作方式
- 基于其内部状态 (`ChatState``SubMind` 的思考结果)、观察到的信息 (`Observation` 提供的内容) 以及 `InterestChatting` 的状态来决定是否回复、何时回复以及如何回复。 - 基于其内部状态 (`ChatState``SubMind` 的思考结果)、观察到的信息 (`Observation` 提供的内容) 以及 `InterestChatting` 的状态来决定是否回复、何时回复以及如何回复。
- **消息缓冲 (Message Caching)**: - **消息缓冲 (Message Caching)**:
- `message_buffer` 模块会对某些传入消息进行临时缓存,尤其是在处理连续的多部分消息(如多张图片)时。 - `message_buffer` 模块会对某些传入消息进行临时缓存,尤其是在处理连续的多部分消息(如多张图片)时。
- 这个缓冲机制发生在 `HeartFCProcessor` 处理流程中,确保消息的完整性,然后才进行后续的存储和兴趣计算。 - 这个缓冲机制发生在 `HeartFCMessageReceiver` 处理流程中,确保消息的完整性,然后才进行后续的存储和兴趣计算。
- 缓存的消息最终仍会流向对应的 `ChatStream`(与 `SubHeartflow` 关联),但核心的消息处理与回复决策仍然是分离的步骤。 - 缓存的消息最终仍会流向对应的 `ChatStream`(与 `SubHeartflow` 关联),但核心的消息处理与回复决策仍然是分离的步骤。
## 2. 核心控制与状态管理 (Core Control and State Management) ## 2. 核心控制与状态管理 (Core Control and State Management)
@@ -148,7 +148,7 @@ c HeartFChatting工作方式
- **管理对象**: 每个 `SubHeartflow` 实例内部维护其 `ChatStateInfo`,包含当前的 `ChatState` - **管理对象**: 每个 `SubHeartflow` 实例内部维护其 `ChatStateInfo`,包含当前的 `ChatState`
- **状态及含义**: - **状态及含义**:
- `ChatState.ABSENT` (不参与/没在看): 初始或停用状态。子心流不观察新信息,不进行思考,也不回复。 - `ChatState.ABSENT` (不参与/没在看): 初始或停用状态。子心流不观察新信息,不进行思考,也不回复。
- `ChatState.CHAT` (随便看看/水群): 普通聊天模式。激活 `NormalChatInstance` - `ChatState.NORMAL` (随便看看/水群): 普通聊天模式。激活 `NormalChatInstance`
* `ChatState.FOCUSED` (专注/认真聊天): 专注聊天模式。激活 `HeartFlowChatInstance` * `ChatState.FOCUSED` (专注/认真聊天): 专注聊天模式。激活 `HeartFlowChatInstance`
- **选择**: 子心流可以根据外部指令(来自 `SubHeartflowManager`)或内部逻辑(未来的扩展)选择进入 `ABSENT` 状态(不回复不观察),或进入 `CHAT` / `FOCUSED` 中的一种回复模式。 - **选择**: 子心流可以根据外部指令(来自 `SubHeartflowManager`)或内部逻辑(未来的扩展)选择进入 `ABSENT` 状态(不回复不观察),或进入 `CHAT` / `FOCUSED` 中的一种回复模式。
- **状态转换机制** (由 `SubHeartflowManager` 驱动,更细致的说明): - **状态转换机制** (由 `SubHeartflowManager` 驱动,更细致的说明):
@@ -156,7 +156,7 @@ c HeartFChatting工作方式
- **`ABSENT` -> `CHAT` (激活闲聊)**: - **`ABSENT` -> `CHAT` (激活闲聊)**:
- **触发条件**: `Heartflow` 的主状态 (`MaiState`) 允许 `CHAT` 模式,且当前 `CHAT` 状态的子心流数量未达上限。 - **触发条件**: `Heartflow` 的主状态 (`MaiState`) 允许 `CHAT` 模式,且当前 `CHAT` 状态的子心流数量未达上限。
- **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用大模型(LLM)。LLM 读取该群聊的近期内容和结合自身个性信息,判断是否"想"在该群开始聊天。 - **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_chat` 方法调用大模型(LLM)。LLM 读取该群聊的近期内容和结合自身个性信息,判断是否"想"在该群开始聊天。
- **执行**: 若 LLM 判断为是,且名额未满,`SubHeartflowManager` 调用 `change_chat_state(ChatState.CHAT)` - **执行**: 若 LLM 判断为是,且名额未满,`SubHeartflowManager` 调用 `change_chat_state(ChatState.NORMAL)`
- **`CHAT` -> `FOCUSED` (激活专注)**: - **`CHAT` -> `FOCUSED` (激活专注)**:
- **触发条件**: 子心流处于 `CHAT` 状态,其内部维护的"开屎热聊"概率 (`InterestChatting.start_hfc_probability`) 达到预设阈值(表示对当前聊天兴趣浓厚),同时 `Heartflow` 的主状态允许 `FOCUSED` 模式,且 `FOCUSED` 名额未满。 - **触发条件**: 子心流处于 `CHAT` 状态,其内部维护的"开屎热聊"概率 (`InterestChatting.start_hfc_probability`) 达到预设阈值(表示对当前聊天兴趣浓厚),同时 `Heartflow` 的主状态允许 `FOCUSED` 模式,且 `FOCUSED` 名额未满。
- **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_focus` 方法定期检查满足条件的 `CHAT` 子心流。 - **判定机制**: `SubHeartflowManager` 中的 `sbhf_absent_into_focus` 方法定期检查满足条件的 `CHAT` 子心流。

View File

@@ -41,7 +41,7 @@ class APIBotConfig:
allow_focus_mode: bool # 是否允许专注聊天状态 allow_focus_mode: bool # 是否允许专注聊天状态
base_normal_chat_num: int # 最多允许多少个群进行普通聊天 base_normal_chat_num: int # 最多允许多少个群进行普通聊天
base_focused_chat_num: int # 最多允许多少个群进行专注聊天 base_focused_chat_num: int # 最多允许多少个群进行专注聊天
chat.observation_context_size: int # 观察到的最长上下文大小 observation_context_size: int # 观察到的最长上下文大小
message_buffer: bool # 是否启用消息缓冲 message_buffer: bool # 是否启用消息缓冲
ban_words: List[str] # 禁止词列表 ban_words: List[str] # 禁止词列表
ban_msgs_regex: List[str] # 禁止消息的正则表达式列表 ban_msgs_regex: List[str] # 禁止消息的正则表达式列表
@@ -128,7 +128,7 @@ class APIBotConfig:
llm_reasoning: Dict[str, Any] # 推理模型配置 llm_reasoning: Dict[str, Any] # 推理模型配置
llm_normal: Dict[str, Any] # 普通模型配置 llm_normal: Dict[str, Any] # 普通模型配置
llm_topic_judge: Dict[str, Any] # 主题判断模型配置 llm_topic_judge: Dict[str, Any] # 主题判断模型配置
model.summary: Dict[str, Any] # 总结模型配置 summary: Dict[str, Any] # 总结模型配置
vlm: Dict[str, Any] # VLM模型配置 vlm: Dict[str, Any] # VLM模型配置
llm_heartflow: Dict[str, Any] # 心流模型配置 llm_heartflow: Dict[str, Any] # 心流模型配置
llm_observation: Dict[str, Any] # 观察模型配置 llm_observation: Dict[str, Any] # 观察模型配置
@@ -203,7 +203,7 @@ class APIBotConfig:
"llm_reasoning", "llm_reasoning",
"llm_normal", "llm_normal",
"llm_topic_judge", "llm_topic_judge",
"model.summary", "summary",
"vlm", "vlm",
"llm_heartflow", "llm_heartflow",
"llm_observation", "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.heart_flow.utils_chat import get_chat_type_and_target_info
from src.chat.message_receive.chat_stream import ChatStream from src.chat.message_receive.chat_stream import ChatStream
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp 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.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.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
import time import time
@@ -281,7 +281,6 @@ class DefaultExpressor:
in_mind_reply, in_mind_reply,
target_message, target_message,
) -> str: ) -> str:
individuality = Individuality.get_instance()
prompt_personality = individuality.get_prompt(x_person=0, level=2) prompt_personality = individuality.get_prompt(x_person=0, level=2)
# Determine if it's a group chat # Determine if it's a group chat
@@ -294,7 +293,7 @@ class DefaultExpressor:
message_list_before_now = get_raw_msg_before_timestamp_with_chat( message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id, chat_id=chat_stream.stream_id,
timestamp=time.time(), 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( chat_talking_prompt = await build_readable_messages(
message_list_before_now, message_list_before_now,

View File

@@ -36,24 +36,6 @@ def init_prompt() -> None:
""" """
Prompt(learn_style_prompt, "learn_style_prompt") 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 = """ learn_grammar_prompt = """
{chat_str} {chat_str}
@@ -278,44 +260,6 @@ class ExpressionLearner:
expressions.append((chat_id, situation, style)) expressions.append((chat_id, situation, style))
return expressions 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() init_prompt()

View File

@@ -3,7 +3,7 @@ import contextlib
import time import time
import traceback import traceback
from collections import deque 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 ChatStream
from src.chat.message_receive.chat_stream import chat_manager from src.chat.message_receive.chat_stream import chat_manager
from rich.traceback import install 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.message import MessageRecv
from ..message_receive.storage import MessageStorage from ..message_receive.storage import MessageStorage
from ..utils.utils import is_mentioned_bot_in_message from ..utils.utils import is_mentioned_bot_in_message
from maim_message import Seg
from src.chat.heart_flow.heartflow import heartflow from src.chat.heart_flow.heartflow import heartflow
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from ..message_receive.chat_stream import chat_manager 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 return interested_rate, is_mentioned
def _get_message_type(message: MessageRecv) -> str: # def _get_message_type(message: MessageRecv) -> str:
"""获取消息类型 # """获取消息类型
Args: # Args:
message: 消息对象 # message: 消息对象
Returns: # Returns:
str: 消息类型 # str: 消息类型
""" # """
if message.message_segment.type != "seglist": # if message.message_segment.type != "seglist":
return message.message_segment.type # return message.message_segment.type
if ( # if (
isinstance(message.message_segment.data, list) # isinstance(message.message_segment.data, list)
and all(isinstance(x, Seg) for x in message.message_segment.data) # and all(isinstance(x, Seg) for x in message.message_segment.data)
and len(message.message_segment.data) == 1 # and len(message.message_segment.data) == 1
): # ):
return message.message_segment.data[0].type # return message.message_segment.data[0].type
return "seglist" # return "seglist"
def _check_ban_words(text: str, chat, userinfo) -> bool: def _check_ban_words(text: str, chat, userinfo) -> bool:
@@ -141,7 +140,7 @@ def _check_ban_regex(text: str, chat, userinfo) -> bool:
return False return False
class HeartFCProcessor: class HeartFCMessageReceiver:
"""心流处理器,负责处理接收到的消息并计算兴趣度""" """心流处理器,负责处理接收到的消息并计算兴趣度"""
def __init__(self): def __init__(self):

View File

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

View File

@@ -1,14 +1,10 @@
from typing import List, Optional, Any 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.heart_flow.observation.observation import Observation
from src.chat.focus_chat.info.info_base import InfoBase from src.chat.focus_chat.info.info_base import InfoBase
from src.chat.focus_chat.info.action_info import ActionInfo from src.chat.focus_chat.info.action_info import ActionInfo
from .base_processor import BaseProcessor from .base_processor import BaseProcessor
from src.common.logger_manager import get_logger 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.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 typing import Dict
from src.chat.models.utils_model import LLMRequest from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
@@ -55,10 +51,7 @@ class ActionProcessor(BaseProcessor):
# 处理Observation对象 # 处理Observation对象
if observations: if observations:
for obs in observations: for obs in observations:
if isinstance(obs, HFCloopObservation): if isinstance(obs, HFCloopObservation):
# 创建动作信息 # 创建动作信息
action_info = ActionInfo() action_info = ActionInfo()
action_changes = await self.analyze_loop_actions(obs) action_changes = await self.analyze_loop_actions(obs)
@@ -75,7 +68,6 @@ class ActionProcessor(BaseProcessor):
return processed_infos return processed_infos
async def analyze_loop_actions(self, obs: HFCloopObservation) -> Dict[str, List[str]]: async def analyze_loop_actions(self, obs: HFCloopObservation) -> Dict[str, List[str]]:
"""分析最近的循环内容并决定动作的增减 """分析最近的循环内容并决定动作的增减

View File

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

View File

@@ -5,7 +5,7 @@ from src.config.config import global_config
import time import time
import traceback import traceback
from src.common.logger_manager import get_logger 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.prompt_builder import Prompt, global_prompt_manager
from src.chat.message_receive.chat_stream import chat_manager from src.chat.message_receive.chat_stream import chat_manager
from src.chat.person_info.relationship_manager import relationship_manager from src.chat.person_info.relationship_manager import relationship_manager
@@ -21,8 +21,9 @@ logger = get_logger("processor")
def init_prompt(): def init_prompt():
indentify_prompt = """ indentify_prompt = """
你的名字是{bot_name},你是一个AI但是你伪装成了一个人类你的人格是{prompt_personality} {name_block}
的头像形象是一只橙色的鱼,头上有绿色的树叶 是一个AI但是你伪装成了一个人类你的人格是{prompt_personality}
{indentify_block}
{relation_prompt} {relation_prompt}
{memory_str} {memory_str}
@@ -125,16 +126,22 @@ class SelfProcessor(BaseProcessor):
# hfcloop_observe_info = observation.get_observe_info() # hfcloop_observe_info = observation.get_observe_info()
pass pass
individuality = Individuality.get_instance() nickname_str = ""
personality_block = individuality.get_prompt(x_person=2, level=2) 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 = "" relation_prompt = ""
for person in person_list: for person in person_list:
relation_prompt += await relationship_manager.build_relationship_info(person, is_id=True) relation_prompt += await relationship_manager.build_relationship_info(person, is_id=True)
prompt = (await global_prompt_manager.get_prompt_async("indentify_prompt")).format( prompt = (await global_prompt_manager.get_prompt_async("indentify_prompt")).format(
bot_name=individuality.name, name_block=name_block,
prompt_personality=personality_block, prompt_personality=personality_block,
indentify_block=identity_block,
memory_str=memory_str, memory_str=memory_str,
relation_prompt=relation_prompt, relation_prompt=relation_prompt,
time_now=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), 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 from src.config.config import global_config
import time import time
from src.common.logger_manager import get_logger 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.prompt_builder import Prompt, global_prompt_manager
from src.tools.tool_use import ToolUser from src.tools.tool_use import ToolUser
from src.chat.utils.json_utils import process_llm_tool_calls 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) 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) # 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.focus_chat.planners.actions.base_action import BaseAction, _ACTION_REGISTRY
from src.chat.heart_flow.observation.observation import Observation from src.chat.heart_flow.observation.observation import Observation
from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor
from src.chat.message_receive.chat_stream import ChatStream 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 from src.common.logger_manager import get_logger
import importlib import importlib
import pkgutil import pkgutil

View File

@@ -1,12 +1,9 @@
import asyncio import asyncio
import traceback import traceback
from src.common.logger_manager import get_logger 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 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.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.message_receive.chat_stream import ChatStream
from src.chat.heart_flow.heartflow import heartflow from src.chat.heart_flow.heartflow import heartflow
from src.chat.heart_flow.sub_heartflow import ChatState from src.chat.heart_flow.sub_heartflow import ChatState
@@ -61,8 +58,6 @@ class ExitFocusChatAction(BaseAction):
self._shutting_down = shutting_down self._shutting_down = shutting_down
self.chat_id = chat_stream.stream_id self.chat_id = chat_stream.stream_id
async def handle_action(self) -> Tuple[bool, str]: async def handle_action(self) -> Tuple[bool, str]:
""" """
处理退出专注聊天的情况 处理退出专注聊天的情况
@@ -95,7 +90,6 @@ class ExitFocusChatAction(BaseAction):
logger.warning(f"{self.log_prefix} {warning_msg}") logger.warning(f"{self.log_prefix} {warning_msg}")
return False, warning_msg return False, warning_msg
return True, status_message return True, status_message
except asyncio.CancelledError: except asyncio.CancelledError:

View File

@@ -3,7 +3,7 @@ import traceback
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.chat.utils.timer_calculator import Timer from src.chat.utils.timer_calculator import Timer
from src.chat.focus_chat.planners.actions.base_action import BaseAction, register_action 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.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp 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.chat.focus_chat.info.structured_info import StructuredInfo
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager 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 from src.chat.focus_chat.planners.action_manager import ActionManager
logger = get_logger("planner") logger = get_logger("planner")
@@ -122,7 +122,7 @@ class ActionPlanner:
observed_messages = info.get_talking_message() observed_messages = info.get_talking_message()
observed_messages_str = info.get_talking_message_str_truncate() observed_messages_str = info.get_talking_message_str_truncate()
chat_type = info.get_chat_type() chat_type = info.get_chat_type()
is_group_chat = (chat_type == "group") is_group_chat = chat_type == "group"
elif isinstance(info, MindInfo): elif isinstance(info, MindInfo):
current_mind = info.get_current_mind() current_mind = info.get_current_mind()
elif isinstance(info, CycleInfo): elif isinstance(info, CycleInfo):
@@ -141,13 +141,9 @@ class ActionPlanner:
action = "no_reply" action = "no_reply"
reasoning = "没有可用的动作" reasoning = "没有可用的动作"
return { return {
"action_result": { "action_result": {"action_type": action, "action_data": action_data, "reasoning": reasoning},
"action_type": action,
"action_data": action_data,
"reasoning": reasoning
},
"current_mind": current_mind, "current_mind": current_mind,
"observed_messages": observed_messages "observed_messages": observed_messages,
} }
# --- 构建提示词 (调用修改后的 PromptBuilder 方法) --- # --- 构建提示词 (调用修改后的 PromptBuilder 方法) ---
@@ -271,7 +267,6 @@ class ActionPlanner:
else: else:
mind_info_block = "你刚参与聊天" mind_info_block = "你刚参与聊天"
individuality = Individuality.get_instance()
personality_block = individuality.get_prompt(x_person=2, level=2) personality_block = individuality.get_prompt(x_person=2, level=2)
action_options_block = "" 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.common.logger_manager import get_logger
from src.chat.heart_flow.mai_state_manager import MaiStateManager, MaiStateInfo from src.chat.heart_flow.mai_state_manager import MaiStateManager, MaiStateInfo
from src.chat.heart_flow.subheartflow_manager import SubHeartflowManager from src.chat.heart_flow.subheartflow_manager import SubHeartflowManager
from src.config.config import global_config
logger = get_logger("background_tasks") logger = get_logger("background_tasks")
@@ -94,13 +94,6 @@ class BackgroundTaskManager:
f"清理任务已启动 间隔:{CLEANUP_INTERVAL_SECONDS}s", f"清理任务已启动 间隔:{CLEANUP_INTERVAL_SECONDS}s",
"_cleanup_task", "_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 # 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: for task_func, log_level, log_msg, task_attr_name in task_configs:
# 检查任务变量是否存在且未完成 # 检查任务变量是否存在且未完成
@@ -183,7 +189,6 @@ class BackgroundTaskManager:
logger.info("检测到离线,停用所有子心流") logger.info("检测到离线,停用所有子心流")
await self.subheartflow_manager.deactivate_all_subflows() await self.subheartflow_manager.deactivate_all_subflows()
async def _perform_cleanup_work(self): async def _perform_cleanup_work(self):
"""执行子心流清理任务 """执行子心流清理任务
1. 获取需要清理的不活跃子心流列表 1. 获取需要清理的不活跃子心流列表
@@ -209,18 +214,15 @@ class BackgroundTaskManager:
# 记录最终清理结果 # 记录最终清理结果
logger.info(f"[清理任务] 清理完成, 共停止 {stopped_count}/{len(flows_to_stop)} 个子心流") logger.info(f"[清理任务] 清理完成, 共停止 {stopped_count}/{len(flows_to_stop)} 个子心流")
# --- 新增兴趣评估工作函数 --- # --- 新增兴趣评估工作函数 ---
async def _perform_into_focus_work(self): async def _perform_into_focus_work(self):
"""执行一轮子心流兴趣评估与提升检查。""" """执行一轮子心流兴趣评估与提升检查。"""
# 直接调用 subheartflow_manager 的方法,并传递当前状态信息 # 直接调用 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): 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) await _run_periodic_loop(task_name="State Update", interval=interval, task_func=self._perform_state_update_work)
async def _run_cleanup_cycle(self): async def _run_cleanup_cycle(self):
await _run_periodic_loop( await _run_periodic_loop(
task_name="Subflow Cleanup", interval=CLEANUP_INTERVAL_SECONDS, task_func=self._perform_cleanup_work 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): class ChatState(enum.Enum):
ABSENT = "没在看群" ABSENT = "没在看群"
CHAT = "随便水群" NORMAL = "随便水群"
FOCUSED = "认真水群" FOCUSED = "认真水群"
class ChatStateInfo: class ChatStateInfo:
def __init__(self): def __init__(self):
self.chat_status: ChatState = ChatState.CHAT self.chat_status: ChatState = ChatState.NORMAL
self.current_state_time = 120 self.current_state_time = 120
self.mood_manager = mood_manager self.mood_manager = mood_manager

View File

@@ -1,9 +1,6 @@
from src.chat.heart_flow.sub_heartflow import SubHeartflow, ChatState 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 src.common.logger_manager import get_logger
from typing import Any, Optional 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.mai_state_manager import MaiStateInfo, MaiStateManager
from src.chat.heart_flow.subheartflow_manager import SubHeartflowManager from src.chat.heart_flow.subheartflow_manager import SubHeartflowManager
from src.chat.heart_flow.background_tasks import BackgroundTaskManager # Import BackgroundTaskManager 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 typing import List, Tuple, Optional
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 src.manager.mood_manager import mood_manager
from src.config.config import global_config
logger = get_logger("mai_state") 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): class MaiState(enum.Enum):
""" """
聊天状态: 聊天状态:
@@ -141,10 +130,6 @@ class MaiStateManager:
) )
next_state = resolved_candidate 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: if next_state is not None and next_state != current_status:
return next_state return next_state
else: else:

View File

@@ -57,7 +57,7 @@ class ChattingObservation(Observation):
self.talking_message_str_truncate = "" self.talking_message_str_truncate = ""
self.name = global_config.bot.nickname self.name = global_config.bot.nickname
self.nick_name = global_config.bot.alias_names 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.overlap_len = global_config.focus_chat.compressed_length
self.mid_memories = [] self.mid_memories = []
self.max_mid_memory_len = global_config.focus_chat.compress_length_limit self.max_mid_memory_len = global_config.focus_chat.compress_length_limit

View File

@@ -2,7 +2,7 @@ from .observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
import asyncio import asyncio
import time import time
from typing import Optional, List, Dict, Tuple, Callable, Coroutine from typing import Optional, List, Dict, Tuple
import traceback import traceback
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.message import MessageRecv
@@ -23,7 +23,6 @@ class SubHeartflow:
self, self,
subheartflow_id, subheartflow_id,
mai_states: MaiStateInfo, mai_states: MaiStateInfo,
hfc_no_reply_callback: Callable[[], Coroutine[None, None, None]],
): ):
"""子心流初始化函数 """子心流初始化函数
@@ -35,7 +34,6 @@ class SubHeartflow:
# 基础属性,两个值是一样的 # 基础属性,两个值是一样的
self.subheartflow_id = subheartflow_id self.subheartflow_id = subheartflow_id
self.chat_id = subheartflow_id self.chat_id = subheartflow_id
self.hfc_no_reply_callback = hfc_no_reply_callback
# 麦麦的状态 # 麦麦的状态
self.mai_states = mai_states self.mai_states = mai_states
@@ -216,7 +214,7 @@ class SubHeartflow:
state_changed = False state_changed = False
log_prefix = f"[{self.log_prefix}]" log_prefix = f"[{self.log_prefix}]"
if new_state == ChatState.CHAT: if new_state == ChatState.NORMAL:
logger.debug(f"{log_prefix} 准备进入或保持 普通聊天 状态") logger.debug(f"{log_prefix} 准备进入或保持 普通聊天 状态")
if await self._start_normal_chat(): if await self._start_normal_chat():
logger.debug(f"{log_prefix} 成功进入或保持 NormalChat 状态。") logger.debug(f"{log_prefix} 成功进入或保持 NormalChat 状态。")

View File

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

View File

@@ -11,7 +11,6 @@ import jieba
import networkx as nx import networkx as nx
import numpy as np import numpy as np
from collections import Counter from collections import Counter
from ...common.database.database import memory_db as db
from ...chat.models.utils_model import LLMRequest from ...chat.models.utils_model import LLMRequest
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.chat.memory_system.sample_distribution import MemoryBuildScheduler # 分布生成器 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.chat.message_receive.message import MessageRecv
from src.experimental.only_message_process import MessageProcessor from src.experimental.only_message_process import MessageProcessor
from src.experimental.PFC.pfc_manager import PFCManager 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.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.config.config import global_config from src.config.config import global_config
@@ -23,7 +23,7 @@ class ChatBot:
self.bot = None # bot 实例引用 self.bot = None # bot 实例引用
self._started = False self._started = False
self.mood_manager = mood_manager # 获取情绪管理器单例 self.mood_manager = mood_manager # 获取情绪管理器单例
self.heartflow_processor = HeartFCProcessor() # 新增 self.heartflow_message_receiver = HeartFCMessageReceiver() # 新增
# 创建初始化PFC管理器的任务会在_ensure_started时执行 # 创建初始化PFC管理器的任务会在_ensure_started时执行
self.only_process_chat = MessageProcessor() self.only_process_chat = MessageProcessor()
@@ -110,11 +110,11 @@ class ChatBot:
# 禁止PFC进入普通的心流消息处理逻辑 # 禁止PFC进入普通的心流消息处理逻辑
else: else:
logger.trace("进入普通心流私聊处理") logger.trace("进入普通心流私聊处理")
await self.heartflow_processor.process_message(message_data) await self.heartflow_message_receiver.process_message(message_data)
# 群聊默认进入心流消息处理逻辑 # 群聊默认进入心流消息处理逻辑
else: else:
logger.trace(f"检测到群聊消息群ID: {group_info.group_id}") 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: if template_group_name:
async with global_prompt_manager.async_message_scope(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: class NormalChat:
def __init__(self, chat_stream: ChatStream, interest_dict: dict = {}): def __init__(self, chat_stream: ChatStream, interest_dict: dict = None):
"""初始化 NormalChat 实例。只进行同步操作。""" """初始化 NormalChat 实例。只进行同步操作。"""
# Basic info from chat_stream (sync) # 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 self.stream_name = chat_manager.get_stream_name(self.stream_id) or self.stream_id
# Interest dict # Interest dict
self.interest_dict = interest_dict self.interest_dict = interest_dict or {}
# --- Initialize attributes (defaults) --- # --- Initialize attributes (defaults) ---
self.is_group_chat: bool = False self.is_group_chat: bool = False
@@ -200,7 +200,6 @@ class NormalChat:
logger.info(f"[{self.stream_name}] 兴趣监控任务被取消或置空,退出") logger.info(f"[{self.stream_name}] 兴趣监控任务被取消或置空,退出")
break break
items_to_process = list(self.interest_dict.items()) items_to_process = list(self.interest_dict.items())
if not items_to_process: if not items_to_process:
continue continue

View File

@@ -49,7 +49,7 @@ class ClassicalWillingManager(BaseWillingManager):
# 检查群组权限(如果是群聊) # 检查群组权限(如果是群聊)
if ( if (
willing_info.group_info 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 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: if w_info.is_emoji:
probability *= global_config.normal_chat.emoji_response_penalty 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 probability /= global_config.normal_chat.down_frequency_rate
self.temporary_willing = current_willing self.temporary_willing = current_willing

View File

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

View File

@@ -127,7 +127,7 @@ class InfoCatcher:
Messages.select() Messages.select()
.where((Messages.chat_id == chat_id_val) & (Messages.message_id < message_id_val)) .where((Messages.chat_id == chat_id_val) & (Messages.message_id < message_id_val))
.order_by(Messages.time.desc()) .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) return list(messages_before_query)

View File

@@ -6,16 +6,13 @@ from collections import Counter
import jieba import jieba
import numpy as np import numpy as np
from maim_message import UserInfo from maim_message import UserInfo
from pymongo.errors import PyMongoError
from src.common.logger import get_module_logger from src.common.logger import get_module_logger
from src.manager.mood_manager import mood_manager from src.manager.mood_manager import mood_manager
from ..message_receive.message import MessageRecv from ..message_receive.message import MessageRecv
from ..models.utils_model import LLMRequest from ..models.utils_model import LLMRequest
from .typo_generator import ChineseTypoGenerator from .typo_generator import ChineseTypoGenerator
from ...common.database.database import db
from ...config.config import global_config from ...config.config import global_config
from ...common.database.database_model import Messages
from ...common.message_repository import find_messages, count_messages from ...common.message_repository import find_messages, count_messages
logger = get_module_logger("chat_utils") 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): def get_recent_group_detailed_plain_text(chat_stream_id: str, limit: int = 12, combine=False):
filter_query = {"chat_id": chat_stream_id} filter_query = {"chat_id": chat_stream_id}
sort_order = [("time", -1)] sort_order = [("time", -1)]
recent_messages = find_messages( recent_messages = find_messages(message_filter=filter_query, sort=sort_order, limit=limit)
message_filter=filter_query,
sort=sort_order,
limit=limit
)
if not recent_messages: if not recent_messages:
return [] 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} filter_query = {"chat_id": chat_stream_id}
sort_order = [("time", -1)] sort_order = [("time", -1)]
recent_messages = find_messages( recent_messages = find_messages(message_filter=filter_query, sort=sort_order, limit=limit)
message_filter=filter_query,
sort=sort_order,
limit=limit
)
if not recent_messages: if not recent_messages:
return [] return []
who_chat_in_group = [] who_chat_in_group = []
for msg_db_data in recent_messages: for msg_db_data in recent_messages:
user_info = UserInfo.from_dict({ user_info = UserInfo.from_dict(
{
"platform": msg_db_data["user_platform"], "platform": msg_db_data["user_platform"],
"user_id": msg_db_data["user_id"], "user_id": msg_db_data["user_id"],
"user_nickname": msg_db_data["user_nickname"], "user_nickname": msg_db_data["user_nickname"],
"user_cardname": msg_db_data.get("user_cardname", "") "user_cardname": msg_db_data.get("user_cardname", ""),
}) }
)
if ( if (
(user_info.platform, user_info.user_id) != sender (user_info.platform, user_info.user_id) != sender
and user_info.user_id != global_config.bot.qq_account and user_info.user_id != global_config.bot.qq_account
@@ -579,7 +570,6 @@ def count_messages_between(start_time: float, end_time: float, stream_id: str) -
# 使用message_repository中的count_messages和find_messages函数 # 使用message_repository中的count_messages和find_messages函数
# 构建查询条件 # 构建查询条件
filter_query = {"chat_id": stream_id, "time": {"$gt": start_time, "$lte": end_time}} filter_query = {"chat_id": stream_id, "time": {"$gt": start_time, "$lte": end_time}}

View File

@@ -14,10 +14,9 @@ from rich.traceback import install
from src.config.config_base import ConfigBase from src.config.config_base import ConfigBase
from src.config.official_configs import ( from src.config.official_configs import (
BotConfig, BotConfig,
ChatTargetConfig,
PersonalityConfig, PersonalityConfig,
IdentityConfig, IdentityConfig,
PlatformsConfig, ExpressionConfig,
ChatConfig, ChatConfig,
NormalChatConfig, NormalChatConfig,
FocusChatConfig, FocusChatConfig,
@@ -30,6 +29,7 @@ from src.config.official_configs import (
TelemetryConfig, TelemetryConfig,
ExperimentalConfig, ExperimentalConfig,
ModelConfig, ModelConfig,
MessageReceiveConfig,
) )
install(extra_lines=3) install(extra_lines=3)
@@ -139,14 +139,14 @@ class Config(ConfigBase):
MMC_VERSION: str = field(default=MMC_VERSION, repr=False, init=False) # 硬编码的版本信息 MMC_VERSION: str = field(default=MMC_VERSION, repr=False, init=False) # 硬编码的版本信息
bot: BotConfig bot: BotConfig
chat_target: ChatTargetConfig
personality: PersonalityConfig personality: PersonalityConfig
identity: IdentityConfig identity: IdentityConfig
platforms: PlatformsConfig
chat: ChatConfig chat: ChatConfig
message_receive: MessageReceiveConfig
normal_chat: NormalChatConfig normal_chat: NormalChatConfig
focus_chat: FocusChatConfig focus_chat: FocusChatConfig
emoji: EmojiConfig emoji: EmojiConfig
expression: ExpressionConfig
memory: MemoryConfig memory: MemoryConfig
mood: MoodConfig mood: MoodConfig
keyword_reaction: KeywordReactionConfig 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 @dataclass
class PersonalityConfig(ConfigBase): class PersonalityConfig(ConfigBase):
"""人格配置类""" """人格配置类"""
@@ -50,9 +33,6 @@ class PersonalityConfig(ConfigBase):
personality_core: str personality_core: str
"""核心人格""" """核心人格"""
expression_style: str
"""表达风格"""
personality_sides: list[str] = field(default_factory=lambda: []) personality_sides: list[str] = field(default_factory=lambda: [])
"""人格侧写""" """人格侧写"""
@@ -80,32 +60,17 @@ class IdentityConfig(ConfigBase):
"""身份特征""" """身份特征"""
@dataclass
class PlatformsConfig(ConfigBase):
"""平台配置类"""
qq: str
"""QQ适配器连接URL配置"""
@dataclass @dataclass
class ChatConfig(ConfigBase): class ChatConfig(ConfigBase):
"""聊天配置类""" """聊天配置类"""
allow_focus_mode: bool = True chat_mode: str = "normal"
"""是否允许专注聊天状态""" """聊天模式"""
base_normal_chat_num: int = 3
"""最多允许多少个群进行普通聊天"""
base_focused_chat_num: int = 2 @dataclass
"""最多允许多少个群进行专注聊天""" class MessageReceiveConfig(ConfigBase):
"""消息接收配置类"""
observation_context_size: int = 12
"""可观察到的最长上下文大小,超过这个值的上下文会被压缩"""
message_buffer: bool = True
"""消息缓冲器"""
ban_words: set[str] = field(default_factory=lambda: set()) ban_words: set[str] = field(default_factory=lambda: set())
"""过滤词列表""" """过滤词列表"""
@@ -124,6 +89,12 @@ class NormalChatConfig(ConfigBase):
选择普通模型的概率为 1 - reasoning_normal_model_probability 选择普通模型的概率为 1 - reasoning_normal_model_probability
""" """
max_context_size: int = 15
"""上下文长度"""
message_buffer: bool = True
"""消息缓冲器"""
emoji_chance: float = 0.2 emoji_chance: float = 0.2
"""发送表情包的基础概率""" """发送表情包的基础概率"""
@@ -139,6 +110,9 @@ class NormalChatConfig(ConfigBase):
response_interested_rate_amplifier: float = 1.0 response_interested_rate_amplifier: float = 1.0
"""回复兴趣度放大系数""" """回复兴趣度放大系数"""
talk_frequency_down_groups: list[str] = field(default_factory=lambda: [])
"""降低回复频率的群组"""
down_frequency_rate: float = 3.0 down_frequency_rate: float = 3.0
"""降低回复频率的群组回复意愿降低系数""" """降低回复频率的群组回复意愿降低系数"""
@@ -162,6 +136,9 @@ class FocusChatConfig(ConfigBase):
default_decay_rate_per_second: float = 0.98 default_decay_rate_per_second: float = 0.98
"""默认衰减率,越大衰减越快""" """默认衰减率,越大衰减越快"""
observation_context_size: int = 12
"""可观察到的最长上下文大小,超过这个值的上下文会被压缩"""
consecutive_no_reply_threshold: int = 3 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 @dataclass
class EmojiConfig(ConfigBase): class EmojiConfig(ConfigBase):
"""表情包配置类""" """表情包配置类"""
@@ -340,11 +331,8 @@ class TelemetryConfig(ConfigBase):
class ExperimentalConfig(ConfigBase): class ExperimentalConfig(ConfigBase):
"""实验功能配置类""" """实验功能配置类"""
# enable_friend_chat: bool = False enable_friend_chat: bool = False
# """是否启用好友聊天""" """是否启用好友聊天"""
# talk_allowed_private: set[str] = field(default_factory=lambda: set())
# """允许聊天的私聊列表"""
pfc_chatting: bool = False pfc_chatting: bool = False
"""是否启用PFC""" """是否启用PFC"""

View File

@@ -5,7 +5,7 @@ from src.chat.models.utils_model import LLMRequest
from src.config.config import global_config from src.config.config import global_config
from src.experimental.PFC.chat_observer import ChatObserver from src.experimental.PFC.chat_observer import ChatObserver
from src.experimental.PFC.pfc_utils import get_items_from_json 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.observation_info import ObservationInfo
from src.experimental.PFC.conversation_info import ConversationInfo from src.experimental.PFC.conversation_info import ConversationInfo
from src.chat.utils.chat_message_builder import build_readable_messages from src.chat.utils.chat_message_builder import build_readable_messages
@@ -113,7 +113,7 @@ class ActionPlanner:
max_tokens=1500, max_tokens=1500,
request_type="action_planning", 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.name = global_config.bot.nickname
self.private_name = private_name self.private_name = private_name
self.chat_observer = ChatObserver.get_instance(stream_id, 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.config.config import global_config
from src.experimental.PFC.chat_observer import ChatObserver from src.experimental.PFC.chat_observer import ChatObserver
from src.experimental.PFC.pfc_utils import get_items_from_json 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.conversation_info import ConversationInfo
from src.experimental.PFC.observation_info import ObservationInfo from src.experimental.PFC.observation_info import ObservationInfo
from src.chat.utils.chat_message_builder import build_readable_messages 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" 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.name = global_config.bot.nickname
self.nick_name = global_config.bot.alias_names self.nick_name = global_config.bot.alias_names
self.private_name = private_name 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.config.config import global_config
from src.experimental.PFC.chat_observer import ChatObserver from src.experimental.PFC.chat_observer import ChatObserver
from src.experimental.PFC.reply_checker import ReplyChecker 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 .observation_info import ObservationInfo
from .conversation_info import ConversationInfo from .conversation_info import ConversationInfo
from src.chat.utils.chat_message_builder import build_readable_messages from src.chat.utils.chat_message_builder import build_readable_messages
@@ -92,7 +92,7 @@ class ReplyGenerator:
max_tokens=300, max_tokens=300,
request_type="reply_generation", 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.name = global_config.bot.nickname
self.private_name = private_name self.private_name = private_name
self.chat_observer = ChatObserver.get_instance(stream_id, 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 .chat_observer import ChatObserver
from .conversation_info import ConversationInfo 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 from src.config.config import global_config
import time import time
import asyncio 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 numpy import double
from .personality import Personality from .personality import Personality
from .identity import Identity from .identity import Identity
from .expression_style import PersonalityExpression
import random import random
from rich.traceback import install from rich.traceback import install
@@ -12,36 +13,15 @@ install(extra_lines=3)
class Individuality: class Individuality:
"""个体特征管理类""" """个体特征管理类"""
_instance = None
def __init__(self): def __init__(self):
if Individuality._instance is not None:
raise RuntimeError("Individuality 类是单例,请使用 get_instance() 方法获取实例。")
# 正常初始化实例属性 # 正常初始化实例属性
self.personality: Optional[Personality] = None self.personality: Optional[Personality] = None
self.identity: Optional[Identity] = None self.identity: Optional[Identity] = None
self.express_style: PersonalityExpression = PersonalityExpression()
self.name = "" self.name = ""
@classmethod async def initialize(
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(
self, self,
bot_nickname: str, bot_nickname: str,
personality_core: str, personality_core: str,
@@ -76,6 +56,8 @@ class Individuality:
identity_detail=identity_detail, height=height, weight=weight, age=age, gender=gender, appearance=appearance 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 self.name = bot_nickname
def to_dict(self) -> dict: def to_dict(self) -> dict:
@@ -88,7 +70,7 @@ class Individuality:
@classmethod @classmethod
def from_dict(cls, data: dict) -> "Individuality": def from_dict(cls, data: dict) -> "Individuality":
"""从字典创建个体特征实例""" """从字典创建个体特征实例"""
instance = cls.get_instance() instance = cls()
if data.get("personality"): if data.get("personality"):
instance.personality = Personality.from_dict(data["personality"]) instance.personality = Personality.from_dict(data["personality"])
if data.get("identity"): if data.get("identity"):
@@ -176,6 +158,10 @@ class Individuality:
identity_parts.append(f"年龄大约{self.identity.age}") identity_parts.append(f"年龄大约{self.identity.age}")
if self.identity.gender: if self.identity.gender:
identity_parts.append(f"性别是{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: if identity_parts:
details_str = "".join(identity_parts) details_str = "".join(identity_parts)
@@ -252,3 +238,6 @@ class Individuality:
elif factor == "neuroticism": elif factor == "neuroticism":
return self.personality.neuroticism return self.personality.neuroticism
return None return None
individuality = Individuality()

View File

@@ -17,9 +17,9 @@ with open(config_path, "r", encoding="utf-8") as f:
config = toml.load(f) config = toml.load(f)
# 现在可以导入src模块 # 现在可以导入src模块
from src.individuality.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa E402 from individuality.not_using.scene import get_scene_by_factor, PERSONALITY_SCENES # noqa E402
from src.individuality.questionnaire import FACTOR_DESCRIPTIONS # noqa E402 from individuality.not_using.questionnaire import FACTOR_DESCRIPTIONS # noqa E402
from src.individuality.offline_llm import LLMRequestOff # noqa E402 from individuality.not_using.offline_llm import LLMRequestOff # noqa E402
# 加载环境变量 # 加载环境变量
env_path = os.path.join(root_path, ".env") 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 .config.config import global_config
from .chat.message_receive.bot import chat_bot from .chat.message_receive.bot import chat_bot
from .common.logger_manager import get_logger 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 .common.server import global_server, Server
from rich.traceback import install from rich.traceback import install
from .chat.focus_chat.expressors.exprssion_learner import expression_learner from .chat.focus_chat.expressors.exprssion_learner import expression_learner
@@ -30,7 +30,7 @@ logger = get_logger("main")
class MainSystem: class MainSystem:
def __init__(self): def __init__(self):
self.hippocampus_manager: HippocampusManager = HippocampusManager.get_instance() self.hippocampus_manager: HippocampusManager = HippocampusManager.get_instance()
self.individuality: Individuality = Individuality.get_instance() self.individuality: Individuality = individuality
# 使用消息API替代直接的FastAPI实例 # 使用消息API替代直接的FastAPI实例
from src.common.message import global_api from src.common.message import global_api
@@ -91,7 +91,7 @@ class MainSystem:
self.app.register_message_handler(chat_bot.message_process) self.app.register_message_handler(chat_bot.message_process)
# 初始化个体特征 # 初始化个体特征
self.individuality.initialize( await self.individuality.initialize(
bot_nickname=global_config.bot.nickname, bot_nickname=global_config.bot.nickname,
personality_core=global_config.personality.personality_core, personality_core=global_config.personality.personality_core,
personality_sides=global_config.personality.personality_sides, personality_sides=global_config.personality.personality_sides,
@@ -104,9 +104,6 @@ class MainSystem:
) )
logger.success("个体特征初始化成功") logger.success("个体特征初始化成功")
# 初始化表达方式
await expression_learner.extract_and_store_personality_expressions()
try: try:
# 启动全局消息管理器 (负责消息发送/排队) # 启动全局消息管理器 (负责消息发送/排队)
await message_manager.start() await message_manager.start()
@@ -169,7 +166,7 @@ class MainSystem:
async def learn_and_store_expression_task(): async def learn_and_store_expression_task():
"""学习并存储表达方式任务""" """学习并存储表达方式任务"""
while True: while True:
await asyncio.sleep(60) await asyncio.sleep(global_config.expression.learning_interval)
print("\033[1;32m[表达方式学习]\033[0m 开始学习表达方式...") print("\033[1;32m[表达方式学习]\033[0m 开始学习表达方式...")
await expression_learner.learn_and_store_expression() await expression_learner.learn_and_store_expression()
print("\033[1;32m[表达方式学习]\033[0m 表达方式学习完成") print("\033[1;32m[表达方式学习]\033[0m 表达方式学习完成")

View File

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

View File

@@ -1,56 +0,0 @@
@echo off
chcp 65001 > nul
setlocal enabledelayedexpansion
cd /d %~dp0
title 麦麦人格生成
cls
echo ======================================
echo 警告提示
echo ======================================
echo 1.这是一个demo系统,仅供体验,特性可能会在将来移除
echo ======================================
echo.
echo ======================================
echo 请选择Python环境:
echo 1 - venv (推荐)
echo 2 - conda
echo ======================================
choice /c 12 /n /m "请输入数字选择(1或2): "
if errorlevel 2 (
echo ======================================
set "CONDA_ENV="
set /p CONDA_ENV="请输入要激活的 conda 环境名称: "
:: 检查输入是否为空
if "!CONDA_ENV!"=="" (
echo 错误:环境名称不能为空
pause
exit /b 1
)
call conda activate !CONDA_ENV!
if errorlevel 1 (
echo 激活 conda 环境失败
pause
exit /b 1
)
echo Conda 环境 "!CONDA_ENV!" 激活成功
python src/individuality/per_bf_gen.py
) else (
if exist "venv\Scripts\python.exe" (
venv\Scripts\python src/individuality/per_bf_gen.py
) else (
echo ======================================
echo 错误: venv环境不存在请先创建虚拟环境
pause
exit /b 1
)
)
endlocal
pause

View File

@@ -1,5 +1,5 @@
[inner] [inner]
version = "2.2.0" version = "2.3.0"
#----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读---- #----以下是给开发人员阅读的,如果你只是部署了麦麦,不需要阅读----
#如果你想要修改配置文件请在修改后将version的值进行变更 #如果你想要修改配置文件请在修改后将version的值进行变更
@@ -15,12 +15,9 @@ version = "2.2.0"
[bot] [bot]
qq_account = 1145141919810 qq_account = 1145141919810
nickname = "麦麦" nickname = "麦麦"
alias_names = ["麦叠", "牢麦"] #该选项还在调试中,暂时未生 alias_names = ["麦叠", "牢麦"] #仅在 专注聊天 有
[chat_target] [personality]
talk_frequency_down_groups = [] #降低回复频率的群号码
[personality] #未完善
personality_core = "用一句话或几句话描述人格的核心特点" # 建议20字以内谁再写3000字小作文敲谁脑袋 personality_core = "用一句话或几句话描述人格的核心特点" # 建议20字以内谁再写3000字小作文敲谁脑袋
personality_sides = [ personality_sides = [
"用一句话或几句话描述人格的一些细节", "用一句话或几句话描述人格的一些细节",
@@ -30,35 +27,30 @@ personality_sides = [
"用一句话或几句话描述人格的一些细节", "用一句话或几句话描述人格的一些细节",
]# 条数任意不能为0, 该选项还在调试中,可能未完全生效 ]# 条数任意不能为0, 该选项还在调试中,可能未完全生效
# 表达方式 # 身份特点 部分选项仅在 专注聊天 有效
expression_style = "描述麦麦说话的表达风格,表达习惯"
[identity] #アイデンティティがない 生まれないらららら [identity] #アイデンティティがない 生まれないらららら
# 兴趣爱好 未完善,有些条目未使用
identity_detail = [ identity_detail = [
"身份特点", "身份特点",
"身份特点", "身份特点",
]# 条数任意不能为0, 该选项还在调试中 ]# 条数任意不能为0
#外貌特征 #外貌特征
age = 18 # 年龄 单位岁 age = 18 # 年龄 单位岁
gender = "女" # 性别 gender = "女" # 性别
height = "170" # 身高单位cm height = "170" # 身高单位cm
weight = "50" # 体重单位kg weight = "50" # 体重单位kg
appearance = "用一句或几句话描述外貌特征" # 外貌特征 该选项还在调试中,暂时未生效 appearance = "用一句或几句话描述外貌特征" # 外貌特征
[platforms] # 必填项目,填写每个平台适配器提供的链接
qq="http://127.0.0.1:18002/api/message"
[chat] #麦麦的聊天通用设置 [chat] #麦麦的聊天通用设置
allow_focus_mode = false # 是否允许专注聊天状态 chat_mode = "normal" # 聊天模式 —— 普通模式normal专注模式focus在普通模式和专注模式之间自动切换
# 是否启用heart_flowC(HFC)模式 # chat_mode = "focus"
# 启用后麦麦会自主选择进入heart_flowC模式持续一段时间进行主动的观察和回复并给出回复比较消耗token # chat_mode = "auto"
chat.observation_context_size = 15 # 观察到的最长上下文大小,建议15太短太长都会导致脑袋尖尖 # 普通模式下麦麦会针对感兴趣的消息进行回复token消耗量较低
message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟 # 专注模式下麦麦会进行主动的观察和回复并给出回复token消耗量较高
# 自动模式下,麦麦会根据消息内容自动切换到专注模式或普通模式
[message_receive]
# 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息 # 以下是消息过滤,可以根据规则过滤特定消息,将不会读取这些消息
ban_words = [ ban_words = [
# "403","张三" # "403","张三"
@@ -74,9 +66,10 @@ ban_msgs_regex = [
[normal_chat] #普通聊天 [normal_chat] #普通聊天
#一般回复参数 #一般回复参数
reasoning_model_probability = 0.3 # 麦麦回答时选择推理模型的概率与之相对的普通模型的概率为1 - reasoning_model_probability reasoning_model_probability = 0.3 # 麦麦回答时选择推理模型的概率与之相对的普通模型的概率为1 - reasoning_model_probability
max_context_size = 15 #上下文长度
emoji_chance = 0.2 # 麦麦一般回复时使用表情包的概率设置为1让麦麦自己决定发不发 emoji_chance = 0.2 # 麦麦一般回复时使用表情包的概率设置为1让麦麦自己决定发不发
thinking_timeout = 120 # 麦麦最长思考时间超过这个时间的思考会放弃往往是api反应太慢 thinking_timeout = 120 # 麦麦最长思考时间超过这个时间的思考会放弃往往是api反应太慢
message_buffer = true # 启用消息缓冲器?启用此项以解决消息的拆分问题,但会使麦麦的回复延迟
willing_mode = "classical" # 回复意愿模式 —— 经典模式classicalmxp模式mxp自定义模式custom需要你自己实现 willing_mode = "classical" # 回复意愿模式 —— 经典模式classicalmxp模式mxp自定义模式custom需要你自己实现
response_willing_amplifier = 1 # 麦麦回复意愿放大系数一般为1 response_willing_amplifier = 1 # 麦麦回复意愿放大系数一般为1
@@ -85,16 +78,23 @@ down_frequency_rate = 3 # 降低回复频率的群组回复意愿降低系数
emoji_response_penalty = 0 # 表情包回复惩罚系数设为0为不回复单个表情包减少单独回复表情包的概率 emoji_response_penalty = 0 # 表情包回复惩罚系数设为0为不回复单个表情包减少单独回复表情包的概率
mentioned_bot_inevitable_reply = false # 提及 bot 必然回复 mentioned_bot_inevitable_reply = false # 提及 bot 必然回复
at_bot_inevitable_reply = false # @bot 必然回复 at_bot_inevitable_reply = false # @bot 必然回复
talk_frequency_down_groups = [] #降低回复频率的群号码
[focus_chat] #专注聊天 [focus_chat] #专注聊天
reply_trigger_threshold = 3.0 # 专注聊天触发阈值,越低越容易进入专注聊天 reply_trigger_threshold = 3.0 # 专注聊天触发阈值,越低越容易进入专注聊天
default_decay_rate_per_second = 0.98 # 默认衰减率,越大衰减越快,越高越难进入专注聊天 default_decay_rate_per_second = 0.98 # 默认衰减率,越大衰减越快,越高越难进入专注聊天
consecutive_no_reply_threshold = 3 # 连续不回复的阈值,越低越容易结束专注聊天 consecutive_no_reply_threshold = 3 # 连续不回复的阈值,越低越容易结束专注聊天
# 以下选项暂时无效 observation_context_size = 15 # 观察到的最长上下文大小,建议15太短太长都会导致脑袋尖尖
compressed_length = 5 # 不能大于chat.observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5 compressed_length = 5 # 不能大于chat.observation_context_size,心流上下文压缩的最短压缩长度超过心流观察到的上下文长度会压缩最短压缩长度为5
compress_length_limit = 5 #最多压缩份数,超过该数值的压缩上下文会被删除 compress_length_limit = 5 #最多压缩份数,超过该数值的压缩上下文会被删除
[expression]
# 表达方式
expression_style = "描述麦麦说话的表达风格,表达习惯"
enable_expression_learning = true # 是否启用表达学习
learning_interval = 300 # 学习间隔 单位秒
[emoji] [emoji]
max_reg_num = 40 # 表情包最大注册数量 max_reg_num = 40 # 表情包最大注册数量
@@ -124,7 +124,7 @@ consolidation_check_percentage = 0.01 # 检查节点比例
#不希望记忆的词,已经记忆的不会受到影响 #不希望记忆的词,已经记忆的不会受到影响
memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ] memory_ban_words = [ "表情包", "图片", "回复", "聊天记录" ]
[mood] [mood] # 仅在 普通聊天 有效
mood_update_interval = 1.0 # 情绪更新间隔 单位秒 mood_update_interval = 1.0 # 情绪更新间隔 单位秒
mood_decay_rate = 0.95 # 情绪衰减率 mood_decay_rate = 0.95 # 情绪衰减率
mood_intensity_factor = 1.0 # 情绪强度因子 mood_intensity_factor = 1.0 # 情绪强度因子
@@ -164,6 +164,7 @@ enable_kaomoji_protection = false # 是否启用颜文字保护
enable = true enable = true
[experimental] #实验性功能 [experimental] #实验性功能
enable_friend_chat = false # 是否启用好友聊天
pfc_chatting = false # 是否启用PFC聊天该功能仅作用于私聊与回复模式独立 pfc_chatting = false # 是否启用PFC聊天该功能仅作用于私聊与回复模式独立
#下面的模型若使用硅基流动则不需要更改使用ds官方则改成.env自定义的宏使用自定义模型则选择定位相似的模型自己填写 #下面的模型若使用硅基流动则不需要更改使用ds官方则改成.env自定义的宏使用自定义模型则选择定位相似的模型自己填写

View File

@@ -1,5 +1,4 @@
import unittest import unittest
from unittest.mock import patch, MagicMock
import datetime import datetime
import sys import sys
import os import os

View File

@@ -1,11 +1,9 @@
import unittest import unittest
import sys import sys
import os import os
import datetime
import time import time
import asyncio import asyncio
import traceback import traceback
import json
import copy import copy
# 添加项目根目录到Python路径 # 添加项目根目录到Python路径

View File

@@ -9,8 +9,6 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")
from src.common.message_repository import find_messages from src.common.message_repository import find_messages
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat
from peewee import SqliteDatabase
from src.common.database.database import db # 导入实际的数据库连接
class TestExtractMessages(unittest.TestCase): class TestExtractMessages(unittest.TestCase):