Merge branch 'dev' into type-fix

This commit is contained in:
keep_running
2025-05-21 02:31:08 +08:00
committed by GitHub
64 changed files with 516 additions and 1312 deletions

View File

@@ -9,6 +9,8 @@ on:
tags: tags:
- "v*.*.*" - "v*.*.*"
- "v*" - "v*"
- "*.*.*"
- "*.*.*-*"
jobs: jobs:
build-amd64: build-amd64:
@@ -47,6 +49,7 @@ jobs:
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=tag type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
@@ -109,6 +112,7 @@ jobs:
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=tag type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
@@ -153,6 +157,7 @@ jobs:
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=tag type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}

1
.gitignore vendored
View File

@@ -40,6 +40,7 @@ config/lpmm_config.toml.bak
(测试版)麦麦生成人格.bat (测试版)麦麦生成人格.bat
(临时版)麦麦开始学习.bat (临时版)麦麦开始学习.bat
src/plugins/utils/statistic.py src/plugins/utils/statistic.py
CLAUDE.md
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

View File

@@ -1,20 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
- **Run Bot**: `python bot.py`
- **Lint**: `ruff check --fix .` or `ruff format .`
- **Run Tests**: `python -m unittest discover -v`
- **Run Single Test**: `python -m unittest src/plugins/message/test.py`
## Code Style
- **Formatting**: Line length 120 chars, use double quotes for strings
- **Imports**: Group standard library, external packages, then internal imports
- **Naming**: snake_case for functions/variables, PascalCase for classes
- **Error Handling**: Use try/except blocks with specific exceptions
- **Types**: Use type hints where possible
- **Docstrings**: Document classes and complex functions
- **Linting**: Follow ruff rules (E, F, B) with ignores E711, E501
When making changes, run `ruff check --fix .` to ensure code follows style guidelines. The codebase uses Ruff for linting and formatting.

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

@@ -1,5 +1,5 @@
""" """
MaiMBot插件系统 MaiBot模块系统
包含聊天、情绪、记忆、日程等功能模块 包含聊天、情绪、记忆、日程等功能模块
""" """

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
@@ -25,10 +25,21 @@ from src.chat.focus_chat.info_processors.self_processor import SelfProcessor
from src.chat.focus_chat.planners.planner import ActionPlanner from src.chat.focus_chat.planners.planner import ActionPlanner
from src.chat.focus_chat.planners.action_manager import ActionManager from src.chat.focus_chat.planners.action_manager import ActionManager
from src.chat.focus_chat.working_memory.working_memory import WorkingMemory from src.chat.focus_chat.working_memory.working_memory import WorkingMemory
from src.config.config import global_config
install(extra_lines=3) install(extra_lines=3)
# 定义处理器映射
PROCESSOR_CLASSES = {
"ChattingInfoProcessor": ChattingInfoProcessor,
"MindProcessor": MindProcessor,
"ToolProcessor": ToolProcessor,
"WorkingMemoryProcessor": WorkingMemoryProcessor,
"SelfProcessor": SelfProcessor,
}
WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒 WAITING_TIME_THRESHOLD = 300 # 等待新消息时间阈值,单位秒
EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发 EMOJI_SEND_PRO = 0.3 # 设置一个概率,比如 30% 才真的发
@@ -89,6 +100,29 @@ class HeartFChatting:
observe_id=self.stream_id, working_memory=self.working_memory observe_id=self.stream_id, working_memory=self.working_memory
) )
# 根据配置文件和默认规则确定启用的处理器
PROCESSOR_NAME_TO_CONFIG_KEY_MAP = {
"SelfProcessor": "self_identify_processor",
"ToolProcessor": "tool_use_processor",
"WorkingMemoryProcessor": "working_memory_processor",
}
self.enabled_processor_names: List[str] = []
config_processor_settings = global_config.focus_chat_processor # 获取处理器配置,若不存在则为空字典
for proc_name in PROCESSOR_CLASSES.keys():
config_key = PROCESSOR_NAME_TO_CONFIG_KEY_MAP.get(proc_name)
if config_key:
# 此处理器可通过配置控制
# getattr(config_processor_settings, config_key, True)
# 如果config_processor_settings是字典则用 config_processor_settings.get(config_key, True)
if getattr(config_processor_settings, config_key, True): # 默认启用,如果配置中未指定
self.enabled_processor_names.append(proc_name)
else:
# 此处理器不在配置映射中,默认启用
self.enabled_processor_names.append(proc_name)
logger.info(f"{self.log_prefix} 将启用的处理器: {self.enabled_processor_names}")
self.expressor = DefaultExpressor(chat_id=self.stream_id) self.expressor = DefaultExpressor(chat_id=self.stream_id)
self.action_manager = ActionManager() self.action_manager = ActionManager()
self.action_planner = ActionPlanner(log_prefix=self.log_prefix, action_manager=self.action_manager) self.action_planner = ActionPlanner(log_prefix=self.log_prefix, action_manager=self.action_manager)
@@ -149,13 +183,37 @@ class HeartFChatting:
return True return True
def _register_default_processors(self): def _register_default_processors(self):
"""注册默认的信息处理器""" """根据 self.enabled_processor_names 注册信息处理器"""
self.processors.append(ChattingInfoProcessor()) self.processors = [] # 清空已有的
self.processors.append(MindProcessor(subheartflow_id=self.stream_id))
self.processors.append(ToolProcessor(subheartflow_id=self.stream_id)) # self.enabled_processor_names 由 __init__ 保证是一个列表
self.processors.append(WorkingMemoryProcessor(subheartflow_id=self.stream_id)) for name in self.enabled_processor_names: # 'name' is "ChattingInfoProcessor", etc.
self.processors.append(SelfProcessor(subheartflow_id=self.stream_id)) processor_class = PROCESSOR_CLASSES.get(name)
logger.info(f"{self.log_prefix} 已注册默认处理器: {[p.__class__.__name__ for p in self.processors]}") if processor_class:
# 根据处理器类名判断是否需要 subheartflow_id
if name in ["MindProcessor", "ToolProcessor", "WorkingMemoryProcessor", "SelfProcessor"]:
self.processors.append(processor_class(subheartflow_id=self.stream_id))
elif name == "ChattingInfoProcessor":
self.processors.append(processor_class())
else:
# 对于PROCESSOR_CLASSES中定义但此处未明确处理构造的处理器
try:
self.processors.append(processor_class()) # 尝试无参构造
logger.debug(f"{self.log_prefix} 注册处理器 {name} (尝试无参构造).")
except TypeError:
logger.error(
f"{self.log_prefix} 处理器 {name} 构造失败。它可能需要参数(如 subheartflow_id但未在注册逻辑中明确处理。"
)
else:
# 这理论上不应该发生,因为 enabled_processor_names 是从 PROCESSOR_CLASSES 的键生成的
logger.warning(f"{self.log_prefix} 在 PROCESSOR_CLASSES 中未找到名为 '{name}' 的处理器,将跳过注册。")
if self.processors:
logger.info(
f"{self.log_prefix} 已根据配置和默认规则注册处理器: {[p.__class__.__name__ for p in self.processors]}"
)
else:
logger.warning(f"{self.log_prefix} 没有注册任何处理器。这可能是由于配置错误或所有处理器都被禁用了。")
async def start(self): async def start(self):
""" """
@@ -258,6 +316,8 @@ class HeartFChatting:
+ (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "") + (f"\n详情: {'; '.join(timer_strings)}" if timer_strings else "")
) )
await asyncio.sleep(global_config.focus_chat.think_interval)
except asyncio.CancelledError: except asyncio.CancelledError:
# 设置了关闭标志位后被取消是正常流程 # 设置了关闭标志位后被取消是正常流程
if not self._shutting_down: if not self._shutting_down:

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:
@@ -112,7 +111,7 @@ def _check_ban_words(text: str, chat, userinfo) -> bool:
Returns: Returns:
bool: 是否包含过滤词 bool: 是否包含过滤词
""" """
for word in global_config.chat.ban_words: for word in global_config.message_receive.ban_words:
if word in text: if word in text:
chat_name = chat.group_info.group_name if chat.group_info else "私聊" chat_name = chat.group_info.group_name if chat.group_info else "私聊"
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
@@ -132,7 +131,7 @@ def _check_ban_regex(text: str, chat, userinfo) -> bool:
Returns: Returns:
bool: 是否匹配过滤正则 bool: 是否匹配过滤正则
""" """
for pattern in global_config.chat.ban_msgs_regex: for pattern in global_config.message_receive.ban_msgs_regex:
if pattern.search(text): if pattern.search(text):
chat_name = chat.group_info.group_name if chat.group_info else "私聊" chat_name = chat.group_info.group_name if chat.group_info else "私聊"
logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}") logger.info(f"[{chat_name}]{userinfo.user_nickname}:{text}")
@@ -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,20 +1,16 @@
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
from src.chat.utils.utils import get_embedding
import time import time
from typing import Union, Optional from typing import Optional
from src.chat.utils.utils import get_recent_group_speaker from src.chat.utils.utils import get_recent_group_speaker
from src.manager.mood_manager import mood_manager from src.manager.mood_manager import mood_manager
from src.chat.memory_system.Hippocampus import HippocampusManager from src.chat.memory_system.Hippocampus import HippocampusManager
from src.chat.knowledge.knowledge_lib import qa_manager from src.chat.knowledge.knowledge_lib import qa_manager
import random import random
import json
import math
from src.common.database.database_model import Knowledges
logger = get_logger("prompt") logger = get_logger("prompt")
@@ -103,7 +99,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 +107,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 +155,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,
@@ -264,129 +259,6 @@ class PromptBuilder:
return prompt return prompt
async def get_prompt_info_old(self, message: str, threshold: float):
start_time = time.time()
related_info = ""
logger.debug(f"获取知识库内容,元消息:{message[:30]}...,消息长度: {len(message)}")
# 1. 先从LLM获取主题类似于记忆系统的做法
topics = []
# 如果无法提取到主题,直接使用整个消息
if not topics:
logger.info("未能提取到任何主题,使用整个消息进行查询")
embedding = await get_embedding(message, request_type="prompt_build")
if not embedding:
logger.error("获取消息嵌入向量失败")
return ""
related_info = self.get_info_from_db(embedding, limit=3, threshold=threshold)
logger.info(f"知识库检索完成,总耗时: {time.time() - start_time:.3f}")
return related_info
# 2. 对每个主题进行知识库查询
logger.info(f"开始处理{len(topics)}个主题的知识库查询")
# 优化批量获取嵌入向量减少API调用
embeddings = {}
topics_batch = [topic for topic in topics if len(topic) > 0]
if message: # 确保消息非空
topics_batch.append(message)
# 批量获取嵌入向量
embed_start_time = time.time()
for text in topics_batch:
if not text or len(text.strip()) == 0:
continue
try:
embedding = await get_embedding(text, request_type="prompt_build")
if embedding:
embeddings[text] = embedding
else:
logger.warning(f"获取'{text}'的嵌入向量失败")
except Exception as e:
logger.error(f"获取'{text}'的嵌入向量时发生错误: {str(e)}")
logger.info(f"批量获取嵌入向量完成,耗时: {time.time() - embed_start_time:.3f}")
if not embeddings:
logger.error("所有嵌入向量获取失败")
return ""
# 3. 对每个主题进行知识库查询
all_results = []
query_start_time = time.time()
# 首先添加原始消息的查询结果
if message in embeddings:
original_results = self.get_info_from_db(embeddings[message], limit=3, threshold=threshold, return_raw=True)
if original_results:
for result in original_results:
result["topic"] = "原始消息"
all_results.extend(original_results)
logger.info(f"原始消息查询到{len(original_results)}条结果")
# 然后添加每个主题的查询结果
for topic in topics:
if not topic or topic not in embeddings:
continue
try:
topic_results = self.get_info_from_db(embeddings[topic], limit=3, threshold=threshold, return_raw=True)
if topic_results:
# 添加主题标记
for result in topic_results:
result["topic"] = topic
all_results.extend(topic_results)
logger.info(f"主题'{topic}'查询到{len(topic_results)}条结果")
except Exception as e:
logger.error(f"查询主题'{topic}'时发生错误: {str(e)}")
logger.info(f"知识库查询完成,耗时: {time.time() - query_start_time:.3f}秒,共获取{len(all_results)}条结果")
# 4. 去重和过滤
process_start_time = time.time()
unique_contents = set()
filtered_results = []
for result in all_results:
content = result["content"]
if content not in unique_contents:
unique_contents.add(content)
filtered_results.append(result)
# 5. 按相似度排序
filtered_results.sort(key=lambda x: x["similarity"], reverse=True)
# 6. 限制总数量最多10条
filtered_results = filtered_results[:10]
logger.info(
f"结果处理完成,耗时: {time.time() - process_start_time:.3f}秒,过滤后剩余{len(filtered_results)}条结果"
)
# 7. 格式化输出
if filtered_results:
format_start_time = time.time()
grouped_results = {}
for result in filtered_results:
topic = result["topic"]
if topic not in grouped_results:
grouped_results[topic] = []
grouped_results[topic].append(result)
# 按主题组织输出
for topic, results in grouped_results.items():
related_info += f"【主题: {topic}\n"
for _i, result in enumerate(results, 1):
_similarity = result["similarity"]
content = result["content"].strip()
related_info += f"{content}\n"
related_info += "\n"
logger.info(f"格式化输出完成,耗时: {time.time() - format_start_time:.3f}")
logger.info(f"知识库检索总耗时: {time.time() - start_time:.3f}")
return related_info
async def get_prompt_info(self, message: str, threshold: float): async def get_prompt_info(self, message: str, threshold: float):
related_info = "" related_info = ""
start_time = time.time() start_time = time.time()
@@ -406,93 +278,11 @@ class PromptBuilder:
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}") logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
return related_info return related_info
else: else:
logger.debug("从LPMM知识库获取知识失败使用旧版数据库进行检索") logger.debug("从LPMM知识库获取知识失败可能是从未导入过知识,返回空知识...")
knowledge_from_old = await self.get_prompt_info_old(message, threshold=threshold) return "未检索到知识"
related_info += knowledge_from_old
logger.debug(f"获取知识库内容,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}")
return related_info
except Exception as e: except Exception as e:
logger.error(f"获取知识库内容时发生异常: {str(e)}") logger.error(f"获取知识库内容时发生异常: {str(e)}")
try: return "未检索到知识"
knowledge_from_old = await self.get_prompt_info_old(message, threshold=threshold)
related_info += knowledge_from_old
logger.debug(
f"异常后使用旧版数据库获取知识,相关信息:{related_info[:100]}...,信息长度: {len(related_info)}"
)
return related_info
except Exception as e2:
logger.error(f"使用旧版数据库获取知识时也发生异常: {str(e2)}")
return ""
@staticmethod
def get_info_from_db(
query_embedding: list, limit: int = 1, threshold: float = 0.5, return_raw: bool = False
) -> Union[str, list]:
if not query_embedding:
return "" if not return_raw else []
results_with_similarity = []
try:
# Fetch all knowledge entries
# This might be inefficient for very large databases.
# Consider strategies like FAISS or other vector search libraries if performance becomes an issue.
all_knowledges = Knowledges.select()
if not all_knowledges:
return [] if return_raw else ""
query_embedding_magnitude = math.sqrt(sum(x * x for x in query_embedding))
if query_embedding_magnitude == 0: # Avoid division by zero
return "" if not return_raw else []
for knowledge_item in all_knowledges:
try:
db_embedding_str = knowledge_item.embedding
db_embedding = json.loads(db_embedding_str)
if len(db_embedding) != len(query_embedding):
logger.warning(
f"Embedding length mismatch for knowledge ID {knowledge_item.id if hasattr(knowledge_item, 'id') else 'N/A'}. Skipping."
)
continue
# Calculate Cosine Similarity
dot_product = sum(q * d for q, d in zip(query_embedding, db_embedding))
db_embedding_magnitude = math.sqrt(sum(x * x for x in db_embedding))
if db_embedding_magnitude == 0: # Avoid division by zero
similarity = 0.0
else:
similarity = dot_product / (query_embedding_magnitude * db_embedding_magnitude)
if similarity >= threshold:
results_with_similarity.append({"content": knowledge_item.content, "similarity": similarity})
except json.JSONDecodeError:
logger.error(
f"Failed to parse embedding for knowledge ID {knowledge_item.id if hasattr(knowledge_item, 'id') else 'N/A'}"
)
except Exception as e:
logger.error(f"Error processing knowledge item: {e}")
# Sort by similarity in descending order
results_with_similarity.sort(key=lambda x: x["similarity"], reverse=True)
# Limit results
limited_results = results_with_similarity[:limit]
logger.debug(f"知识库查询结果数量 (after Peewee processing): {len(limited_results)}")
if not limited_results:
return "" if not return_raw else []
if return_raw:
return limited_results
else:
return "\n".join(str(result["content"]) for result in limited_results)
except Exception as e:
logger.error(f"Error querying Knowledges with Peewee: {e}")
return "" if not return_raw else []
init_prompt() init_prompt()

View File

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

View File

@@ -1,14 +1,10 @@
from typing import List, Optional, Any from 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]]:
"""分析最近的循环内容并决定动作的增减 """分析最近的循环内容并决定动作的增减
@@ -87,29 +79,29 @@ class ActionProcessor(BaseProcessor):
} }
""" """
result = {"add": [], "remove": []} result = {"add": [], "remove": []}
# 获取最近10次循环 # 获取最近10次循环
recent_cycles = obs.history_loop[-10:] if len(obs.history_loop) > 10 else obs.history_loop recent_cycles = obs.history_loop[-10:] if len(obs.history_loop) > 10 else obs.history_loop
if not recent_cycles: if not recent_cycles:
return result return result
# 统计no_reply的数量 # 统计no_reply的数量
no_reply_count = 0 no_reply_count = 0
reply_sequence = [] # 记录最近的动作序列 reply_sequence = [] # 记录最近的动作序列
for cycle in recent_cycles: for cycle in recent_cycles:
action_type = cycle.loop_plan_info["action_result"]["action_type"] action_type = cycle.loop_plan_info["action_result"]["action_type"]
if action_type == "no_reply": if action_type == "no_reply":
no_reply_count += 1 no_reply_count += 1
reply_sequence.append(action_type == "reply") reply_sequence.append(action_type == "reply")
# 检查no_reply比例 # 检查no_reply比例
if len(recent_cycles) >= 5 and (no_reply_count / len(recent_cycles)) >= 0.8: if len(recent_cycles) >= 5 and (no_reply_count / len(recent_cycles)) >= 0.8:
result["add"].append("exit_focus_chat") result["add"].append("exit_focus_chat")
# 获取最近三次的reply状态 # 获取最近三次的reply状态
last_three = reply_sequence[-3:] if len(reply_sequence) >= 3 else reply_sequence last_three = reply_sequence[-3:] if len(reply_sequence) >= 3 else reply_sequence
# 根据最近的reply情况决定是否移除reply动作 # 根据最近的reply情况决定是否移除reply动作
if len(last_three) >= 3 and all(last_three): if len(last_three) >= 3 and all(last_three):
# 如果最近三次都是reply直接移除 # 如果最近三次都是reply直接移除
@@ -122,5 +114,5 @@ class ActionProcessor(BaseProcessor):
# 如果最近一次是reply20%概率移除 # 如果最近一次是reply20%概率移除
if random.random() < 0.2: if random.random() < 0.2:
result["remove"].append("reply") result["remove"].append("reply")
return result return result

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}
@@ -36,8 +37,8 @@ def init_prompt():
3. 你的自我认同是否有助于你的回答,如果你需要自我相关的信息来帮你参与聊天,请输出,否则请输出十个字以内的简短自我认同 3. 你的自我认同是否有助于你的回答,如果你需要自我相关的信息来帮你参与聊天,请输出,否则请输出十个字以内的简短自我认同
4. 一般情况下不用输出自我认同,只需要输出十几个字的简短自我认同就好,除非有明显需要自我认同的场景 4. 一般情况下不用输出自我认同,只需要输出十几个字的简短自我认同就好,除非有明显需要自我认同的场景
回复的平淡一些,简短一些,说中文,不要浮夸,平淡一些。 思考的平淡一些,简短一些,说中文,不要浮夸,平淡一些。
请注意不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。只输出内容。 请注意不要输出多余内容(包括前后缀,冒号和引号,括号()表情包at或 @等 )。只输出自我认同内容。
""" """
Prompt(indentify_prompt, "indentify_prompt") Prompt(indentify_prompt, "indentify_prompt")
@@ -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:
@@ -105,4 +99,4 @@ class ExitFocusChatAction(BaseAction):
error_msg = f"处理 'exit_focus_chat' 时发生错误: {str(e)}" error_msg = f"处理 'exit_focus_chat' 时发生错误: {str(e)}"
logger.error(f"{self.log_prefix} {error_msg}") logger.error(f"{self.log_prefix} {error_msg}")
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
return False, error_msg return False, error_msg

View File

@@ -3,7 +3,7 @@ import traceback
from src.common.logger_manager import get_logger from src.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")
@@ -92,37 +92,37 @@ class ActionPlanner:
try: try:
# 获取观察信息 # 获取观察信息
extra_info: list[str] = [] extra_info: list[str] = []
# 首先处理动作变更 # 首先处理动作变更
for info in all_plan_info: for info in all_plan_info:
if isinstance(info, ActionInfo) and info.has_changes(): if isinstance(info, ActionInfo) and info.has_changes():
add_actions = info.get_add_actions() add_actions = info.get_add_actions()
remove_actions = info.get_remove_actions() remove_actions = info.get_remove_actions()
reason = info.get_reason() reason = info.get_reason()
# 处理动作的增加 # 处理动作的增加
for action_name in add_actions: for action_name in add_actions:
if action_name in self.action_manager.get_registered_actions(): if action_name in self.action_manager.get_registered_actions():
self.action_manager.add_action_to_using(action_name) self.action_manager.add_action_to_using(action_name)
logger.debug(f"{self.log_prefix}添加动作: {action_name}, 原因: {reason}") logger.debug(f"{self.log_prefix}添加动作: {action_name}, 原因: {reason}")
# 处理动作的移除 # 处理动作的移除
for action_name in remove_actions: for action_name in remove_actions:
self.action_manager.remove_action_from_using(action_name) self.action_manager.remove_action_from_using(action_name)
logger.debug(f"{self.log_prefix}移除动作: {action_name}, 原因: {reason}") logger.debug(f"{self.log_prefix}移除动作: {action_name}, 原因: {reason}")
# 如果当前选择的动作被移除了更新为no_reply # 如果当前选择的动作被移除了更新为no_reply
if action in remove_actions: if action in remove_actions:
action = "no_reply" action = "no_reply"
reasoning = f"之前选择的动作{action}已被移除,原因: {reason}" reasoning = f"之前选择的动作{action}已被移除,原因: {reason}"
# 继续处理其他信息 # 继续处理其他信息
for info in all_plan_info: for info in all_plan_info:
if isinstance(info, ObsInfo): if isinstance(info, ObsInfo):
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):
@@ -134,20 +134,16 @@ class ActionPlanner:
# 获取当前可用的动作 # 获取当前可用的动作
current_available_actions = self.action_manager.get_using_actions() current_available_actions = self.action_manager.get_using_actions()
# 如果没有可用动作直接返回no_reply # 如果没有可用动作直接返回no_reply
if not current_available_actions: if not current_available_actions:
logger.warning(f"{self.log_prefix}没有可用的动作将使用no_reply") logger.warning(f"{self.log_prefix}没有可用的动作将使用no_reply")
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):
""" """
聊天状态: 聊天状态:
@@ -97,7 +86,6 @@ class MaiStateManager:
current_time = time.time() current_time = time.time()
current_status = current_state_info.mai_status current_status = current_state_info.mai_status
time_in_current_status = current_time - current_state_info.last_status_change_time time_in_current_status = current_time - current_state_info.last_status_change_time
_time_since_last_min_check = current_time - current_state_info.last_min_check_time
next_state: Optional[MaiState] = None next_state: Optional[MaiState] = None
def _resolve_offline(candidate_state: MaiState) -> MaiState: def _resolve_offline(candidate_state: MaiState) -> MaiState:
@@ -141,10 +129,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

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

View File

@@ -2,7 +2,7 @@ from .observation.observation import Observation
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation 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
@@ -13,6 +13,7 @@ from src.chat.heart_flow.mai_state_manager import MaiStateInfo
from src.chat.heart_flow.chat_state_info import ChatState, ChatStateInfo from src.chat.heart_flow.chat_state_info import ChatState, ChatStateInfo
from .utils_chat import get_chat_type_and_target_info from .utils_chat import get_chat_type_and_target_info
from .interest_chatting import InterestChatting from .interest_chatting import InterestChatting
from src.config.config import global_config
logger = get_logger("sub_heartflow") logger = get_logger("sub_heartflow")
@@ -23,7 +24,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 +35,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
@@ -89,13 +88,13 @@ class SubHeartflow:
await self.interest_chatting.initialize() await self.interest_chatting.initialize()
logger.debug(f"{self.log_prefix} InterestChatting 实例已初始化。") logger.debug(f"{self.log_prefix} InterestChatting 实例已初始化。")
# 创建并初始化 normal_chat_instance # 根据配置决定初始状态
chat_stream = chat_manager.get_stream(self.chat_id) if global_config.chat.chat_mode == "focus":
if chat_stream: logger.info(f"{self.log_prefix} 配置为 focus 模式,将直接尝试进入 FOCUSED 状态。")
self.normal_chat_instance = NormalChat(chat_stream=chat_stream,interest_dict=self.get_interest_dict()) await self.change_chat_state(ChatState.FOCUSED)
await self.normal_chat_instance.initialize() else: # "auto" 或其他模式保持原有逻辑或默认为 NORMAL
await self.normal_chat_instance.start_chat() logger.info(f"{self.log_prefix} 配置为 auto 或其他模式,将尝试进入 NORMAL 状态。")
logger.info(f"{self.log_prefix} NormalChat 实例已创建并启动。") await self.change_chat_state(ChatState.NORMAL)
def update_last_chat_state_time(self): def update_last_chat_state_time(self):
self.chat_state_last_time = time.time() - self.chat_state_changed_time self.chat_state_last_time = time.time() - self.chat_state_changed_time
@@ -128,10 +127,9 @@ class SubHeartflow:
if not chat_stream: if not chat_stream:
logger.error(f"{log_prefix} 无法获取 chat_stream无法启动 NormalChat。") logger.error(f"{log_prefix} 无法获取 chat_stream无法启动 NormalChat。")
return False return False
if rewind: # 在 rewind 为 True 或 NormalChat 实例尚未创建时,创建新实例
if rewind or not self.normal_chat_instance:
self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict()) self.normal_chat_instance = NormalChat(chat_stream=chat_stream, interest_dict=self.get_interest_dict())
else:
self.normal_chat_instance = NormalChat(chat_stream=chat_stream)
# 进行异步初始化 # 进行异步初始化
await self.normal_chat_instance.initialize() await self.normal_chat_instance.initialize()
@@ -187,9 +185,10 @@ class SubHeartflow:
logger.info(f"{log_prefix} 麦麦准备开始专注聊天...") logger.info(f"{log_prefix} 麦麦准备开始专注聊天...")
try: try:
# 创建 HeartFChatting 实例,并传递 从构造函数传入的 回调函数 # 创建 HeartFChatting 实例,并传递 从构造函数传入的 回调函数
self.heart_fc_instance = HeartFChatting( self.heart_fc_instance = HeartFChatting(
chat_id=self.subheartflow_id, chat_id=self.subheartflow_id,
observations=self.observations, observations=self.observations,
) )
# 初始化并启动 HeartFChatting # 初始化并启动 HeartFChatting
@@ -216,7 +215,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 状态。")
@@ -260,20 +259,6 @@ class SubHeartflow:
f"{log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。" f"{log_prefix} 尝试将状态从 {current_state.value} 变为 {new_state.value},但未成功或未执行更改。"
) )
async def subheartflow_start_working(self):
"""启动子心流的后台任务
功能说明:
- 负责子心流的主要后台循环
- 每30秒检查一次停止标志
"""
logger.trace(f"{self.log_prefix} 子心流开始工作...")
while not self.should_stop:
await asyncio.sleep(30) # 30秒检查一次停止标志
logger.info(f"{self.log_prefix} 子心流后台任务已停止。")
def add_observation(self, observation: Observation): def add_observation(self, observation: Observation):
for existing_obs in self.observations: for existing_obs in self.observations:
if existing_obs.observe_id == observation.observe_id: if existing_obs.observe_id == observation.observe_id:

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,35 +98,25 @@ 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 创建的回调
) )
# 异步初始化 # 首先创建并添加聊天观察者
await new_subflow.initialize()
# 添加聊天观察者
observation = ChattingObservation(chat_id=subheartflow_id) observation = ChattingObservation(chat_id=subheartflow_id)
await observation.initialize() await observation.initialize()
new_subflow.add_observation(observation) new_subflow.add_observation(observation)
# 然后再进行异步初始化,此时 SubHeartflow 内部若需启动 HeartFChatting就能拿到 observation
await new_subflow.initialize()
# 注册子心流 # 注册子心流
self.subheartflows[subheartflow_id] = new_subflow self.subheartflows[subheartflow_id] = new_subflow
heartflow_name = chat_manager.get_stream_name(subheartflow_id) or subheartflow_id heartflow_name = chat_manager.get_stream_name(subheartflow_id) or subheartflow_id
logger.info(f"[{heartflow_name}] 开始接收消息") logger.info(f"[{heartflow_name}] 开始接收消息")
# 启动后台任务
asyncio.create_task(new_subflow.subheartflow_start_working())
return new_subflow return new_subflow
except Exception as e: except Exception as e:
logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True) logger.error(f"创建子心流 {subheartflow_id} 失败: {e}", exc_info=True)
@@ -199,22 +186,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 +204,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 +222,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 +234,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 +263,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 +283,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 +305,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 +325,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

@@ -121,5 +121,5 @@ class QAManager:
found_knowledge = found_knowledge[:MAX_KNOWLEDGE_LENGTH] + "\n" found_knowledge = found_knowledge[:MAX_KNOWLEDGE_LENGTH] + "\n"
return found_knowledge return found_knowledge
else: else:
logger.info("LPMM知识库并未初始化使用旧版数据库进行检索") logger.info("LPMM知识库并未初始化可能是从未导入过知识...")
return None return None

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)
@@ -38,10 +38,8 @@ class NormalChat:
# Interest dict # Interest dict
self.interest_dict = interest_dict self.interest_dict = interest_dict
# --- Initialize attributes (defaults) ---
self.is_group_chat: bool = False self.is_group_chat: bool = False
self.chat_target_info: Optional[dict] = None self.chat_target_info: Optional[dict] = None
# --- End Initialization ---
# Other sync initializations # Other sync initializations
self.gpt = NormalChatGenerator() self.gpt = NormalChatGenerator()
@@ -51,9 +49,6 @@ class NormalChat:
self._chat_task: Optional[asyncio.Task] = None self._chat_task: Optional[asyncio.Task] = None
self._initialized = False # Track initialization status self._initialized = False # Track initialization status
# logger.info(f"[{self.stream_name}] NormalChat 实例 __init__ 完成 (同步部分)。")
# Avoid logging here as stream_name might not be final
async def initialize(self): async def initialize(self):
"""异步初始化,获取聊天类型和目标信息。""" """异步初始化,获取聊天类型和目标信息。"""
if self._initialized: if self._initialized:
@@ -200,7 +195,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
@@ -463,10 +457,11 @@ class NormalChat:
await self.initialize() # Ensure initialized before starting tasks await self.initialize() # Ensure initialized before starting tasks
if self._chat_task is None or self._chat_task.done(): if self._chat_task is None or self._chat_task.done():
logger.info(f"[{self.stream_name}] 开始后台处理初始兴趣消息和轮询任务...") logger.info(f"[{self.stream_name}] 开始回顾消息...")
# Process initial messages first # Process initial messages first
await self._process_initial_interest_messages() await self._process_initial_interest_messages()
# Then start polling task # Then start polling task
logger.info(f"[{self.stream_name}] 开始处理兴趣消息...")
polling_task = asyncio.create_task(self._reply_interested_message()) polling_task = asyncio.create_task(self._reply_interested_message())
polling_task.add_done_callback(lambda t: self._handle_task_completion(t)) polling_task.add_done_callback(lambda t: self._handle_task_completion(t))
self._chat_task = polling_task self._chat_task = polling_task

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"], {
"user_id": msg_db_data["user_id"], "platform": msg_db_data["user_platform"],
"user_nickname": msg_db_data["user_nickname"], "user_id": msg_db_data["user_id"],
"user_cardname": msg_db_data.get("user_cardname", "") "user_nickname": msg_db_data["user_nickname"],
}) "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,14 +570,13 @@ 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}}
try: try:
# 先获取消息数量 # 先获取消息数量
count = count_messages(filter_query) count = count_messages(filter_query)
# 获取消息内容计算总长度 # 获取消息内容计算总长度
messages = find_messages(message_filter=filter_query) messages = find_messages(message_filter=filter_query)
total_length = sum(len(msg.get("processed_plain_text", "")) for msg in messages) total_length = sum(len(msg.get("processed_plain_text", "")) for msg in messages)

View File

@@ -1,312 +0,0 @@
import os
import sys
import requests
from dotenv import load_dotenv
import hashlib
from datetime import datetime
from tqdm import tqdm
from rich.console import Console
from rich.table import Table
from rich.traceback import install
install(extra_lines=3)
# 添加项目根目录到 Python 路径
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
sys.path.append(root_path)
# 现在可以导入src模块
from common.database.database import db # noqa E402
# 加载根目录下的env.edv文件
env_path = os.path.join(root_path, ".env")
if not os.path.exists(env_path):
raise FileNotFoundError(f"配置文件不存在: {env_path}")
load_dotenv(env_path)
class KnowledgeLibrary:
def __init__(self):
self.raw_info_dir = "data/raw_info"
self._ensure_dirs()
self.api_key = os.getenv("SILICONFLOW_KEY")
if not self.api_key:
raise ValueError("SILICONFLOW_API_KEY 环境变量未设置")
self.console = Console()
def _ensure_dirs(self):
"""确保必要的目录存在"""
os.makedirs(self.raw_info_dir, exist_ok=True)
@staticmethod
def read_file(file_path: str) -> str:
"""读取文件内容"""
with open(file_path, "r", encoding="utf-8") as f:
return f.read()
@staticmethod
def split_content(content: str, max_length: int = 512) -> list:
"""将内容分割成适当大小的块,按空行分割
Args:
content: 要分割的文本内容
max_length: 每个块的最大长度
Returns:
list: 分割后的文本块列表
"""
# 按空行分割内容
paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
chunks = []
for para in paragraphs:
para_length = len(para)
# 如果段落长度小于等于最大长度,直接添加
if para_length <= max_length:
chunks.append(para)
else:
# 如果段落超过最大长度,则按最大长度切分
for i in range(0, para_length, max_length):
chunks.append(para[i : i + max_length])
return chunks
def get_embedding(self, text: str) -> list:
"""获取文本的embedding向量"""
url = "https://api.siliconflow.cn/v1/embeddings"
payload = {"model": "BAAI/bge-m3", "input": text, "encoding_format": "float"}
headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
if response.status_code != 200:
print(f"获取embedding失败: {response.text}")
return None
return response.json()["data"][0]["embedding"]
def process_files(self, knowledge_length: int = 512):
"""处理raw_info目录下的所有txt文件"""
txt_files = [f for f in os.listdir(self.raw_info_dir) if f.endswith(".txt")]
if not txt_files:
self.console.print("[red]警告:在 {} 目录下没有找到任何txt文件[/red]".format(self.raw_info_dir))
self.console.print("[yellow]请将需要处理的文本文件放入该目录后再运行程序[/yellow]")
return
total_stats = {"processed_files": 0, "total_chunks": 0, "failed_files": [], "skipped_files": []}
self.console.print(f"\n[bold blue]开始处理知识库文件 - 共{len(txt_files)}个文件[/bold blue]")
for filename in tqdm(txt_files, desc="处理文件进度"):
file_path = os.path.join(self.raw_info_dir, filename)
result = self.process_single_file(file_path, knowledge_length)
self._update_stats(total_stats, result, filename)
self._display_processing_results(total_stats)
def process_single_file(self, file_path: str, knowledge_length: int = 512):
"""处理单个文件"""
result = {"status": "success", "chunks_processed": 0, "error": None}
try:
current_hash = self.calculate_file_hash(file_path)
processed_record = db.processed_files.find_one({"file_path": file_path})
if processed_record:
if processed_record.get("hash") == current_hash:
if knowledge_length in processed_record.get("split_by", []):
result["status"] = "skipped"
return result
content = self.read_file(file_path)
chunks = self.split_content(content, knowledge_length)
for chunk in tqdm(chunks, desc=f"处理 {os.path.basename(file_path)} 的文本块", leave=False):
embedding = self.get_embedding(chunk)
if embedding:
knowledge = {
"content": chunk,
"embedding": embedding,
"source_file": file_path,
"split_length": knowledge_length,
"created_at": datetime.now(),
}
db.knowledges.insert_one(knowledge)
result["chunks_processed"] += 1
split_by = processed_record.get("split_by", []) if processed_record else []
if knowledge_length not in split_by:
split_by.append(knowledge_length)
db.knowledges.processed_files.update_one(
{"file_path": file_path},
{"$set": {"hash": current_hash, "last_processed": datetime.now(), "split_by": split_by}},
upsert=True,
)
except Exception as e:
result["status"] = "failed"
result["error"] = str(e)
return result
@staticmethod
def _update_stats(total_stats, result, filename):
"""更新总体统计信息"""
if result["status"] == "success":
total_stats["processed_files"] += 1
total_stats["total_chunks"] += result["chunks_processed"]
elif result["status"] == "failed":
total_stats["failed_files"].append((filename, result["error"]))
elif result["status"] == "skipped":
total_stats["skipped_files"].append(filename)
def _display_processing_results(self, stats):
"""显示处理结果统计"""
self.console.print("\n[bold green]处理完成!统计信息如下:[/bold green]")
table = Table(show_header=True, header_style="bold magenta")
table.add_column("统计项", style="dim")
table.add_column("数值")
table.add_row("成功处理文件数", str(stats["processed_files"]))
table.add_row("处理的知识块总数", str(stats["total_chunks"]))
table.add_row("跳过的文件数", str(len(stats["skipped_files"])))
table.add_row("失败的文件数", str(len(stats["failed_files"])))
self.console.print(table)
if stats["failed_files"]:
self.console.print("\n[bold red]处理失败的文件:[/bold red]")
for filename, error in stats["failed_files"]:
self.console.print(f"[red]- {filename}: {error}[/red]")
if stats["skipped_files"]:
self.console.print("\n[bold yellow]跳过的文件(已处理):[/bold yellow]")
for filename in stats["skipped_files"]:
self.console.print(f"[yellow]- {filename}[/yellow]")
@staticmethod
def calculate_file_hash(file_path):
"""计算文件的MD5哈希值"""
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def search_similar_segments(self, query: str, limit: int = 5) -> list:
"""搜索与查询文本相似的片段"""
query_embedding = self.get_embedding(query)
if not query_embedding:
return []
# 使用余弦相似度计算
pipeline = [
{
"$addFields": {
"dotProduct": {
"$reduce": {
"input": {"$range": [0, {"$size": "$embedding"}]},
"initialValue": 0,
"in": {
"$add": [
"$$value",
{
"$multiply": [
{"$arrayElemAt": ["$embedding", "$$this"]},
{"$arrayElemAt": [query_embedding, "$$this"]},
]
},
]
},
}
},
"magnitude1": {
"$sqrt": {
"$reduce": {
"input": "$embedding",
"initialValue": 0,
"in": {"$add": ["$$value", {"$multiply": ["$$this", "$$this"]}]},
}
}
},
"magnitude2": {
"$sqrt": {
"$reduce": {
"input": query_embedding,
"initialValue": 0,
"in": {"$add": ["$$value", {"$multiply": ["$$this", "$$this"]}]},
}
}
},
}
},
{"$addFields": {"similarity": {"$divide": ["$dotProduct", {"$multiply": ["$magnitude1", "$magnitude2"]}]}}},
{"$sort": {"similarity": -1}},
{"$limit": limit},
{"$project": {"content": 1, "similarity": 1, "file_path": 1}},
]
results = list(db.knowledges.aggregate(pipeline))
return results
# 创建单例实例
knowledge_library = KnowledgeLibrary()
if __name__ == "__main__":
console = Console()
console.print("[bold green]知识库处理工具[/bold green]")
while True:
console.print("\n请选择要执行的操作:")
console.print("[1] 麦麦开始学习")
console.print("[2] 麦麦全部忘光光(仅知识)")
console.print("[q] 退出程序")
choice = input("\n请输入选项: ").strip()
if choice.lower() == "q":
console.print("[yellow]程序退出[/yellow]")
sys.exit(0)
elif choice == "2":
confirm = input("确定要删除所有知识吗?这个操作不可撤销!(y/n): ").strip().lower()
if confirm == "y":
db.knowledges.delete_many({})
console.print("[green]已清空所有知识![/green]")
continue
elif choice == "1":
if not os.path.exists(knowledge_library.raw_info_dir):
console.print(f"[yellow]创建目录:{knowledge_library.raw_info_dir}[/yellow]")
os.makedirs(knowledge_library.raw_info_dir, exist_ok=True)
# 询问分割长度
while True:
try:
length_input = input("请输入知识分割长度默认512输入q退出回车使用默认值: ").strip()
if length_input.lower() == "q":
break
if not length_input: # 如果直接回车,使用默认值
knowledge_length = 512
break
knowledge_length = int(length_input)
if knowledge_length <= 0:
print("分割长度必须大于0请重新输入")
continue
break
except ValueError:
print("请输入有效的数字")
continue
if length_input.lower() == "q":
continue
# 测试知识库功能
print(f"开始处理知识库文件,使用分割长度: {knowledge_length}...")
knowledge_library.process_files(knowledge_length=knowledge_length)
else:
console.print("[red]无效的选项,请重新选择[/red]")
continue

View File

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

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,8 @@ from src.config.official_configs import (
TelemetryConfig, TelemetryConfig,
ExperimentalConfig, ExperimentalConfig,
ModelConfig, ModelConfig,
FocusChatProcessorConfig,
MessageReceiveConfig,
) )
install(extra_lines=3) install(extra_lines=3)
@@ -139,14 +140,15 @@ 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
focus_chat_processor: FocusChatProcessorConfig
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
"""连续不回复的次数阈值""" """连续不回复的次数阈值"""
@@ -171,6 +148,37 @@ class FocusChatConfig(ConfigBase):
compress_length_limit: int = 5 compress_length_limit: int = 5
"""最多压缩份数,超过该数值的压缩上下文会被删除""" """最多压缩份数,超过该数值的压缩上下文会被删除"""
think_interval: int = 1
"""思考间隔(秒)"""
@dataclass
class FocusChatProcessorConfig(ConfigBase):
"""专注聊天处理器配置类"""
self_identify_processor: bool = True
"""是否启用自我识别处理器"""
tool_use_processor: bool = True
"""是否启用工具使用处理器"""
working_memory_processor: bool = True
"""是否启用工作记忆处理器"""
@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 +348,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

@@ -1,6 +1,9 @@
from typing import Optional from typing import Optional
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
@@ -10,43 +13,22 @@ 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,
personality_sides: list, personality_sides: list,
identity_detail: list, identity_detail: list,
height: int, height: int,
weight: float, weight: double,
age: int, age: int,
gender: str, gender: str,
appearance: str, appearance: str,
@@ -74,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:
@@ -86,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"):
@@ -174,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)
@@ -250,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,40 +0,0 @@
from src.tools.tool_can_use.base_tool import BaseTool
from src.common.logger import get_module_logger
from typing import Any
logger = get_module_logger("get_mid_memory_tool")
class GetMidMemoryTool(BaseTool):
"""从记忆系统中获取相关记忆的工具"""
name = "mid_chat_mem"
description = "之前的聊天内容概述id中获取具体信息如果没有聊天内容概述id就不要使用"
parameters = {
"type": "object",
"properties": {
"id": {"type": "integer", "description": "要查询的聊天记录概述id"},
},
"required": ["id"],
}
async def execute(self, function_args: dict[str, Any], message_txt: str = "") -> dict[str, Any]:
"""执行记忆获取
Args:
function_args: 工具参数
message_txt: 原始消息文本
Returns:
dict: 工具执行结果
"""
try:
id = function_args.get("id")
return {"name": "mid_chat_mem", "content": str(id)}
except Exception as e:
logger.error(f"聊天记录获取工具执行失败: {str(e)}")
return {"name": "mid_chat_mem", "content": f"聊天记录获取失败: {str(e)}"}
# 注册工具
# register_tool(GetMemoryTool)

View File

@@ -1,25 +0,0 @@
from src.tools.tool_can_use.base_tool import BaseTool
from src.common.logger import get_module_logger
from typing import Any
logger = get_module_logger("send_emoji_tool")
class SendEmojiTool(BaseTool):
"""发送表情包的工具"""
name = "send_emoji"
description = "当你觉得需要表达情感,或者帮助表达,可以使用这个工具发送表情包"
parameters = {
"type": "object",
"properties": {"text": {"type": "string", "description": "要发送的表情包描述"}},
"required": ["text"],
}
async def execute(self, function_args: dict[str, Any], message_txt: str = "") -> dict[str, Any]:
text = function_args.get("text", message_txt)
return {
"name": "send_emoji",
"content": text,
}

View File

@@ -1,39 +0,0 @@
from src.tools.tool_can_use.base_tool import BaseTool
from src.common.logger_manager import get_logger
from typing import Dict, Any
from datetime import datetime
import time
logger = get_logger("get_time_date")
class GetCurrentDateTimeTool(BaseTool):
"""获取当前时间、日期、年份和星期的工具"""
name = "get_current_date_time"
description = "当有人询问或者涉及到具体时间或者日期的时候,必须使用这个工具"
parameters = {
"type": "object",
"properties": {},
"required": [],
}
async def execute(self, function_args: Dict[str, Any]) -> Dict[str, Any]:
"""执行获取当前时间、日期、年份和星期
Args:
function_args: 工具参数(此工具不使用)
Returns:
Dict: 工具执行结果
"""
current_time = datetime.now().strftime("%H:%M:%S")
current_date = datetime.now().strftime("%Y-%m-%d")
current_year = datetime.now().strftime("%Y")
current_weekday = datetime.now().strftime("%A")
return {
"type": "time_info",
"id": f"time_info_{time.time()}",
"content": f"当前时间: {current_time}, 日期: {current_date}, 年份: {current_year}, 星期: {current_weekday}",
}

View File

@@ -3,11 +3,6 @@ from src.config.config import global_config
import json import json
from src.common.logger_manager import get_logger from src.common.logger_manager import get_logger
from src.tools.tool_can_use import get_all_tool_definitions, get_tool_instance from src.tools.tool_can_use import get_all_tool_definitions, get_tool_instance
import traceback
from src.chat.person_info.relationship_manager import relationship_manager
from src.chat.utils.utils import parse_text_timestamps
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.heart_flow.observation.chatting_observation import ChattingObservation
logger = get_logger("tool_use") logger = get_logger("tool_use")
@@ -18,41 +13,6 @@ class ToolUser:
model=global_config.model.tool_use, temperature=0.2, max_tokens=1000, request_type="tool_use" model=global_config.model.tool_use, temperature=0.2, max_tokens=1000, request_type="tool_use"
) )
@staticmethod
async def _build_tool_prompt(
message_txt: str, chat_stream: ChatStream = None, observation: ChattingObservation = None
):
"""构建工具使用的提示词
Args:
message_txt: 用户消息文本
subheartflow: 子心流对象
Returns:
str: 构建好的提示词
"""
if observation:
mid_memory_info = observation.mid_memory_info
# print(f"intol111111111111111111111111111111111222222222222mid_memory_info{mid_memory_info}")
# 这些信息应该从调用者传入而不是从self获取
bot_name = global_config.bot.nickname
prompt = ""
prompt += mid_memory_info
prompt += "你正在思考如何回复群里的消息。\n"
prompt += "之前群里进行了如下讨论:\n"
prompt += message_txt
# prompt += f"你注意到{sender_name}刚刚说:{message_txt}\n"
prompt += f"注意你就是{bot_name}{bot_name}是你的名字。根据之前的聊天记录补充问题信息,搜索时避开你的名字。\n"
# prompt += "必须调用 'lpmm_get_knowledge' 工具来获取知识。\n"
prompt += "你现在需要对群里的聊天内容进行回复,请你思考应该使用什么工具,然后选择工具来对消息和你的回复进行处理,你是否需要额外的信息,比如回忆或者搜寻已有的知识,改变关系和情感,或者了解你现在正在做什么。"
prompt = await relationship_manager.convert_all_person_sign_to_person_name(prompt)
prompt = parse_text_timestamps(prompt, mode="lite")
return prompt
@staticmethod @staticmethod
def _define_tools(): def _define_tools():
"""获取所有已注册工具的定义 """获取所有已注册工具的定义
@@ -100,93 +60,3 @@ class ToolUser:
except Exception as e: except Exception as e:
logger.error(f"执行工具调用时发生错误: {str(e)}") logger.error(f"执行工具调用时发生错误: {str(e)}")
return None return None
async def use_tool(self, message_txt: str, chat_stream: ChatStream = None, observation: ChattingObservation = None):
"""使用工具辅助思考,判断是否需要额外信息
Args:
message_txt: 用户消息文本
chat_stream: 聊天流对象
observation: 观察对象(可选)
Returns:
dict: 工具使用结果,包含结构化的信息
"""
try:
# 构建提示词
prompt = await self._build_tool_prompt(
message_txt=message_txt,
chat_stream=chat_stream,
observation=observation,
)
# 定义可用工具
tools = self._define_tools()
logger.trace(f"工具定义: {tools}")
# 使用llm_model_tool发送带工具定义的请求
payload = {
"model": self.llm_model_tool.model_name,
"messages": [{"role": "user", "content": prompt}],
"tools": tools,
"temperature": 0.2,
}
logger.trace(f"发送工具调用请求,模型: {self.llm_model_tool.model_name}")
# 发送请求获取模型是否需要调用工具
response = await self.llm_model_tool._execute_request(
endpoint="/chat/completions", payload=payload, prompt=prompt
)
# 根据返回值数量判断是否有工具调用
if len(response) == 3:
content, reasoning_content, tool_calls = response
# logger.info(f"工具思考: {tool_calls}")
# logger.debug(f"工具思考: {content}")
# 检查响应中工具调用是否有效
if not tool_calls:
logger.debug("模型返回了空的tool_calls列表")
return {"used_tools": False}
tool_calls_str = ""
for tool_call in tool_calls:
tool_calls_str += f"{tool_call['function']['name']}\n"
logger.info(
f"根据:\n{prompt}\n\n内容:{content}\n\n模型请求调用{len(tool_calls)}个工具: {tool_calls_str}"
)
tool_results = []
structured_info = {} # 动态生成键
# 执行所有工具调用
for tool_call in tool_calls:
result = await self._execute_tool_call(tool_call)
if result:
tool_results.append(result)
# 使用工具名称作为键
tool_name = result["name"]
if tool_name not in structured_info:
structured_info[tool_name] = []
structured_info[tool_name].append({"name": result["name"], "content": result["content"]})
# 如果有工具结果,返回结构化的信息
if structured_info:
logger.debug(f"工具调用收集到结构化信息: {json.dumps(structured_info, ensure_ascii=False)}")
return {"used_tools": True, "structured_info": structured_info}
else:
# 没有工具调用
content, reasoning_content = response
logger.debug("模型没有请求调用任何工具")
# 如果没有工具调用或处理失败,直接返回原始思考
return {
"used_tools": False,
}
except Exception as e:
logger.error(f"工具调用过程中出错: {str(e)}")
logger.error(f"工具调用过程中出错: {traceback.format_exc()}")
return {
"used_tools": False,
"error": str(e),
}

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,32 @@ 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 # 连续不回复的阈值,越低越容易结束专注聊天
# 以下选项暂时无效 think_interval = 1 # 思考间隔 单位秒
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 #最多压缩份数,超过该数值的压缩上下文会被删除
[focus_chat_processor] # 专注聊天处理器打开可以实现更多功能但是会增加token消耗
self_identify_processor = true # 是否启用自我识别处理器
tool_use_processor = true # 是否启用工具使用处理器
working_memory_processor = true # 是否启用工作记忆处理器
[expression]
# 表达方式
expression_style = "描述麦麦说话的表达风格,表达习惯"
enable_expression_learning = true # 是否启用表达学习
learning_interval = 300 # 学习间隔 单位秒
[emoji] [emoji]
max_reg_num = 40 # 表情包最大注册数量 max_reg_num = 40 # 表情包最大注册数量
@@ -124,7 +133,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 +173,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):