pull/1273/head
SengokuCola 2025-09-28 02:04:49 +08:00
commit cec2c1830e
30 changed files with 203 additions and 188 deletions

View File

@ -1,26 +1,19 @@
import random from typing import List, Tuple, Type
from typing import List, Tuple, Type, Any
from src.plugin_system import ( from src.plugin_system import (
BasePlugin, BasePlugin,
register_plugin, register_plugin,
BaseAction,
BaseCommand, BaseCommand,
BaseTool,
ComponentInfo, ComponentInfo,
ActionActivationType,
ConfigField, ConfigField,
BaseEventHandler,
EventType,
MaiMessages,
ToolParamType,
ReplyContentType, ReplyContentType,
emoji_api, emoji_api,
) )
from maim_message import Seg from maim_message import Seg
from src.config.config import global_config
from src.common.logger import get_logger from src.common.logger import get_logger
logger = get_logger("emoji_manage_plugin") logger = get_logger("emoji_manage_plugin")
class AddEmojiCommand(BaseCommand): class AddEmojiCommand(BaseCommand):
command_name = "add_emoji" command_name = "add_emoji"
command_description = "添加表情包" command_description = "添加表情包"
@ -51,7 +44,7 @@ class AddEmojiCommand(BaseCommand):
emotions = result.get("emotions", []) emotions = result.get("emotions", [])
replaced = result.get("replaced", False) replaced = result.get("replaced", False)
result_msg = f"表情包 {i+1} 注册成功{'(替换旧表情包)' if replaced else '(新增表情包)'}" result_msg = f"表情包 {i + 1} 注册成功{'(替换旧表情包)' if replaced else '(新增表情包)'}"
if description: if description:
result_msg += f"\n描述: {description}" result_msg += f"\n描述: {description}"
if emotions: if emotions:
@ -61,11 +54,11 @@ class AddEmojiCommand(BaseCommand):
else: else:
fail_count += 1 fail_count += 1
error_msg = result.get("message", "注册失败") error_msg = result.get("message", "注册失败")
results.append(f"表情包 {i+1} 注册失败: {error_msg}") results.append(f"表情包 {i + 1} 注册失败: {error_msg}")
except Exception as e: except Exception as e:
fail_count += 1 fail_count += 1
results.append(f"表情包 {i+1} 注册时发生错误: {str(e)}") results.append(f"表情包 {i + 1} 注册时发生错误: {str(e)}")
# 构建返回消息 # 构建返回消息
total_count = success_count + fail_count total_count = success_count + fail_count
@ -140,6 +133,7 @@ class AddEmojiCommand(BaseCommand):
emoji_base64_list.extend(self.find_and_return_emoji_in_message(seg.data)) emoji_base64_list.extend(self.find_and_return_emoji_in_message(seg.data))
return emoji_base64_list return emoji_base64_list
class ListEmojiCommand(BaseCommand): class ListEmojiCommand(BaseCommand):
"""列表表情包Command - 响应/emoji list命令""" """列表表情包Command - 响应/emoji list命令"""
@ -156,6 +150,7 @@ class ListEmojiCommand(BaseCommand):
# 解析命令参数 # 解析命令参数
import re import re
match = re.match(r"^/emoji list(?:\s+(\d+))?$", self.message.raw_message) match = re.match(r"^/emoji list(?:\s+(\d+))?$", self.message.raw_message)
max_count = 10 # 默认显示10个 max_count = 10 # 默认显示10个
if match and match.group(1): if match and match.group(1):
@ -195,7 +190,7 @@ class ListEmojiCommand(BaseCommand):
display_emojis = all_emojis[:max_count] display_emojis = all_emojis[:max_count]
message_lines.append(f"\n📋 显示前 {len(display_emojis)} 个表情包:") message_lines.append(f"\n📋 显示前 {len(display_emojis)} 个表情包:")
for i, (emoji_base64, description, emotion) in enumerate(display_emojis, 1): for i, (_, description, emotion) in enumerate(display_emojis, 1):
# 截断过长的描述 # 截断过长的描述
short_desc = description[:50] + "..." if len(description) > 50 else description short_desc = description[:50] + "..." if len(description) > 50 else description
message_lines.append(f"{i}. {short_desc} [{emotion}]") message_lines.append(f"{i}. {short_desc} [{emotion}]")
@ -257,7 +252,7 @@ class DeleteEmojiCommand(BaseCommand):
count_after = result.get("count_after", 0) count_after = result.get("count_after", 0)
emotions = result.get("emotions", []) emotions = result.get("emotions", [])
result_msg = f"表情包 {i+1} 删除成功" result_msg = f"表情包 {i + 1} 删除成功"
if description: if description:
result_msg += f"\n描述: {description}" result_msg += f"\n描述: {description}"
if emotions: if emotions:
@ -268,11 +263,11 @@ class DeleteEmojiCommand(BaseCommand):
else: else:
fail_count += 1 fail_count += 1
error_msg = result.get("message", "删除失败") error_msg = result.get("message", "删除失败")
results.append(f"表情包 {i+1} 删除失败: {error_msg}") results.append(f"表情包 {i + 1} 删除失败: {error_msg}")
except Exception as e: except Exception as e:
fail_count += 1 fail_count += 1
results.append(f"表情包 {i+1} 删除时发生错误: {str(e)}") results.append(f"表情包 {i + 1} 删除时发生错误: {str(e)}")
# 构建返回消息 # 构建返回消息
total_count = success_count + fail_count total_count = success_count + fail_count

View File

@ -16,7 +16,6 @@ from src.chat.brain_chat.brain_planner import BrainPlanner
from src.chat.planner_actions.action_modifier import ActionModifier from src.chat.planner_actions.action_modifier import ActionModifier
from src.chat.planner_actions.action_manager import ActionManager from src.chat.planner_actions.action_manager import ActionManager
from src.chat.heart_flow.hfc_utils import CycleDetail from src.chat.heart_flow.hfc_utils import CycleDetail
from src.chat.heart_flow.hfc_utils import send_typing, stop_typing
from src.chat.express.expression_learner import expression_learner_manager from src.chat.express.expression_learner import expression_learner_manager
from src.person_info.person_info import Person from src.person_info.person_info import Person
from src.plugin_system.base.component_types import EventType, ActionInfo from src.plugin_system.base.component_types import EventType, ActionInfo
@ -97,7 +96,6 @@ class BrainChatting:
self.more_plan = False self.more_plan = False
async def start(self): async def start(self):
"""检查是否需要启动主循环,如果未激活则启动。""" """检查是否需要启动主循环,如果未激活则启动。"""
@ -171,9 +169,7 @@ class BrainChatting:
if len(recent_messages_list) >= 1: if len(recent_messages_list) >= 1:
self.last_read_time = time.time() self.last_read_time = time.time()
await self._observe( await self._observe(recent_messages_list=recent_messages_list)
recent_messages_list=recent_messages_list
)
else: else:
# Normal模式消息数量不足等待 # Normal模式消息数量不足等待
@ -233,11 +229,11 @@ class BrainChatting:
async def _observe( async def _observe(
self, # interest_value: float = 0.0, self, # interest_value: float = 0.0,
recent_messages_list: Optional[List["DatabaseMessages"]] = None recent_messages_list: Optional[List["DatabaseMessages"]] = None,
) -> bool: # sourcery skip: merge-else-if-into-elif, remove-redundant-if ) -> bool: # sourcery skip: merge-else-if-into-elif, remove-redundant-if
if recent_messages_list is None: if recent_messages_list is None:
recent_messages_list = [] recent_messages_list = []
reply_text = "" # 初始化reply_text变量避免UnboundLocalError _reply_text = "" # 初始化reply_text变量避免UnboundLocalError
async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()): async with global_prompt_manager.async_message_scope(self.chat_stream.context.get_template_name()):
await self.expression_learner.trigger_learning_for_chat() await self.expression_learner.trigger_learning_for_chat()
@ -334,7 +330,7 @@ class BrainChatting:
"taken_time": time.time(), "taken_time": time.time(),
} }
) )
reply_text = reply_text_from_reply _reply_text = reply_text_from_reply
else: else:
# 没有回复信息构建纯动作的loop_info # 没有回复信息构建纯动作的loop_info
loop_info = { loop_info = {
@ -347,7 +343,7 @@ class BrainChatting:
"taken_time": time.time(), "taken_time": time.time(),
}, },
} }
reply_text = action_reply_text _reply_text = action_reply_text
self.end_cycle(loop_info, cycle_timers) self.end_cycle(loop_info, cycle_timers)
self.print_cycle_info(cycle_timers) self.print_cycle_info(cycle_timers)
@ -484,7 +480,6 @@ class BrainChatting:
"""执行单个动作的通用函数""" """执行单个动作的通用函数"""
try: try:
with Timer(f"动作{action_planner_info.action_type}", cycle_timers): with Timer(f"动作{action_planner_info.action_type}", cycle_timers):
if action_planner_info.action_type == "no_reply": if action_planner_info.action_type == "no_reply":
# 直接处理no_action逻辑不再通过动作系统 # 直接处理no_action逻辑不再通过动作系统
reason = action_planner_info.reasoning or "选择不回复" reason = action_planner_info.reasoning or "选择不回复"
@ -517,7 +512,9 @@ class BrainChatting:
if not success or not llm_response or not llm_response.reply_set: if not success or not llm_response or not llm_response.reply_set:
if action_planner_info.action_message: if action_planner_info.action_message:
logger.info(f"{action_planner_info.action_message.processed_plain_text} 的回复生成失败") logger.info(
f"{action_planner_info.action_message.processed_plain_text} 的回复生成失败"
)
else: else:
logger.info("回复生成失败") logger.info("回复生成失败")
return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None} return {"action_type": "reply", "success": False, "reply_text": "", "loop_info": None}

View File

@ -307,7 +307,9 @@ class BrainPlanner:
if chat_target_info: if chat_target_info:
# 构建聊天上下文描述 # 构建聊天上下文描述
chat_context_description = f"你正在和 {chat_target_info.person_name or chat_target_info.user_nickname or '对方'} 聊天中" chat_context_description = (
f"你正在和 {chat_target_info.person_name or chat_target_info.user_nickname or '对方'} 聊天中"
)
# 构建动作选项块 # 构建动作选项块
action_options_block = await self._build_action_options_block(current_available_actions) action_options_block = await self._build_action_options_block(current_available_actions)

View File

@ -10,11 +10,14 @@ from src.common.logger import get_logger
from src.common.database.database_model import Expression from src.common.database.database_model import Expression
from src.llm_models.utils_model import LLMRequest from src.llm_models.utils_model import LLMRequest
from src.config.config import model_config, global_config from src.config.config import model_config, global_config
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_with_chat_inclusive, build_anonymous_messages, build_bare_messages from src.chat.utils.chat_message_builder import (
get_raw_msg_by_timestamp_with_chat_inclusive,
build_anonymous_messages,
build_bare_messages,
)
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 get_chat_manager from src.chat.message_receive.chat_stream import get_chat_manager
from json_repair import repair_json from json_repair import repair_json
from src.chat.utils.utils import get_embedding
MAX_EXPRESSION_COUNT = 300 MAX_EXPRESSION_COUNT = 300
@ -99,7 +102,9 @@ class ExpressionLearner:
self.last_learning_time: float = time.time() self.last_learning_time: float = time.time()
# 学习参数 # 学习参数
_, self.enable_learning, self.learning_intensity = global_config.expression.get_expression_config_for_chat(self.chat_id) _, self.enable_learning, self.learning_intensity = global_config.expression.get_expression_config_for_chat(
self.chat_id
)
self.min_messages_for_learning = 15 / self.learning_intensity # 触发学习所需的最少消息数 self.min_messages_for_learning = 15 / self.learning_intensity # 触发学习所需的最少消息数
self.min_learning_interval = 150 / self.learning_intensity self.min_learning_interval = 150 / self.learning_intensity
@ -237,17 +242,42 @@ class ExpressionLearner:
return [] return []
learnt_expressions = res learnt_expressions = res
learnt_expressions_str = "" learnt_expressions_str = ""
for _chat_id, situation, style, context, context_words, full_context, full_context_embedding in learnt_expressions: for (
_chat_id,
situation,
style,
_context,
_context_words,
_full_context,
_full_context_embedding,
) in learnt_expressions:
learnt_expressions_str += f"{situation}->{style}\n" learnt_expressions_str += f"{situation}->{style}\n"
logger.info(f"{self.chat_name} 学习到表达风格:\n{learnt_expressions_str}") logger.info(f"{self.chat_name} 学习到表达风格:\n{learnt_expressions_str}")
# 按chat_id分组 # 按chat_id分组
chat_dict: Dict[str, List[Dict[str, Any]]] = {} chat_dict: Dict[str, List[Dict[str, Any]]] = {}
for chat_id, situation, style, context, context_words, full_context, full_context_embedding in learnt_expressions: for (
chat_id,
situation,
style,
context,
context_words,
full_context,
full_context_embedding,
) in learnt_expressions:
if chat_id not in chat_dict: if chat_id not in chat_dict:
chat_dict[chat_id] = [] chat_dict[chat_id] = []
chat_dict[chat_id].append({"situation": situation, "style": style, "context": context, "context_words": context_words, "full_context": full_context, "full_context_embedding": full_context_embedding}) chat_dict[chat_id].append(
{
"situation": situation,
"style": style,
"context": context,
"context_words": context_words,
"full_context": full_context,
"full_context_embedding": full_context_embedding,
}
)
current_time = time.time() current_time = time.time()
@ -300,11 +330,13 @@ class ExpressionLearner:
expr.delete_instance() expr.delete_instance()
return learnt_expressions return learnt_expressions
async def match_expression_context(self, expression_pairs: List[Tuple[str, str]], random_msg_match_str: str) -> List[Tuple[str, str, str]]: async def match_expression_context(
self, expression_pairs: List[Tuple[str, str]], random_msg_match_str: str
) -> List[Tuple[str, str, str]]:
# 为expression_pairs逐个条目赋予编号并构建成字符串 # 为expression_pairs逐个条目赋予编号并构建成字符串
numbered_pairs = [] numbered_pairs = []
for i, (situation, style) in enumerate(expression_pairs, 1): for i, (situation, style) in enumerate(expression_pairs, 1):
numbered_pairs.append(f"{i}. 当\"{situation}\"时,使用\"{style}\"") numbered_pairs.append(f'{i}. 当"{situation}"时,使用"{style}"')
expression_pairs_str = "\n".join(numbered_pairs) expression_pairs_str = "\n".join(numbered_pairs)
@ -325,14 +357,14 @@ class ExpressionLearner:
try: try:
response = response.strip() response = response.strip()
# 检查是否已经是标准JSON数组格式 # 检查是否已经是标准JSON数组格式
if response.startswith('[') and response.endswith(']'): if response.startswith("[") and response.endswith("]"):
match_responses = json.loads(response) match_responses = json.loads(response)
else: else:
# 尝试直接解析多个JSON对象 # 尝试直接解析多个JSON对象
try: try:
# 如果是多个JSON对象用逗号分隔包装成数组 # 如果是多个JSON对象用逗号分隔包装成数组
if response.startswith('{') and not response.startswith('['): if response.startswith("{") and not response.startswith("["):
response = '[' + response + ']' response = "[" + response + "]"
match_responses = json.loads(response) match_responses = json.loads(response)
else: else:
# 使用repair_json处理响应 # 使用repair_json处理响应
@ -394,7 +426,9 @@ class ExpressionLearner:
return matched_expressions return matched_expressions
async def learn_expression(self, num: int = 10) -> Optional[List[Tuple[str, str, str, List[str], str, List[float]]]]: async def learn_expression(
self, num: int = 10
) -> Optional[List[Tuple[str, str, str, List[str], str, List[float]]]]:
"""从指定聊天流学习表达方式 """从指定聊天流学习表达方式
Args: Args:
@ -416,12 +450,11 @@ class ExpressionLearner:
if not random_msg or random_msg == []: if not random_msg or random_msg == []:
return None return None
# 转化成str # 转化成str
chat_id: str = random_msg[0].chat_id _chat_id: str = random_msg[0].chat_id
# random_msg_str: str = build_readable_messages(random_msg, timestamp_mode="normal") # random_msg_str: str = build_readable_messages(random_msg, timestamp_mode="normal")
random_msg_str: str = await build_anonymous_messages(random_msg) random_msg_str: str = await build_anonymous_messages(random_msg)
random_msg_match_str: str = await build_bare_messages(random_msg) random_msg_match_str: str = await build_bare_messages(random_msg)
prompt: str = await global_prompt_manager.format_prompt( prompt: str = await global_prompt_manager.format_prompt(
prompt, prompt,
chat_str=random_msg_str, chat_str=random_msg_str,
@ -440,16 +473,21 @@ class ExpressionLearner:
expressions: List[Tuple[str, str]] = self.parse_expression_response(response) expressions: List[Tuple[str, str]] = self.parse_expression_response(response)
matched_expressions: List[Tuple[str, str, str]] = await self.match_expression_context(expressions, random_msg_match_str) matched_expressions: List[Tuple[str, str, str]] = await self.match_expression_context(
expressions, random_msg_match_str
)
split_matched_expressions: List[Tuple[str, str, str, List[str]]] = self.split_expression_context(matched_expressions) split_matched_expressions: List[Tuple[str, str, str, List[str]]] = self.split_expression_context(
matched_expressions
)
split_matched_expressions_w_emb = [] split_matched_expressions_w_emb = []
full_context_embedding: List[float] = await self.get_full_context_embedding(random_msg_match_str) full_context_embedding: List[float] = await self.get_full_context_embedding(random_msg_match_str)
for situation, style, context, context_words in split_matched_expressions: for situation, style, context, context_words in split_matched_expressions:
split_matched_expressions_w_emb.append((self.chat_id, situation, style, context, context_words, random_msg_match_str,full_context_embedding)) split_matched_expressions_w_emb.append(
(self.chat_id, situation, style, context, context_words, random_msg_match_str, full_context_embedding)
)
return split_matched_expressions_w_emb return split_matched_expressions_w_emb
@ -457,7 +495,9 @@ class ExpressionLearner:
embedding, _ = await self.embedding_model.get_embedding(context) embedding, _ = await self.embedding_model.get_embedding(context)
return embedding return embedding
def split_expression_context(self, matched_expressions: List[Tuple[str, str, str]]) -> List[Tuple[str, str, str, List[str]]]: def split_expression_context(
self, matched_expressions: List[Tuple[str, str, str]]
) -> List[Tuple[str, str, str, List[str]]]:
""" """
对matched_expressions中的context部分进行jieba分词 对matched_expressions中的context部分进行jieba分词

View File

@ -208,7 +208,11 @@ class HeartFChatting:
# *控制频率用 # *控制频率用
if mentioned_message: if mentioned_message:
await self._observe(recent_messages_list=recent_messages_list, force_reply_message=mentioned_message) await self._observe(recent_messages_list=recent_messages_list, force_reply_message=mentioned_message)
elif random.random() < global_config.chat.talk_value * frequency_control_manager.get_or_create_frequency_control(self.stream_id).get_talk_frequency_adjust(): elif (
random.random()
< global_config.chat.talk_value
* frequency_control_manager.get_or_create_frequency_control(self.stream_id).get_talk_frequency_adjust()
):
await self._observe(recent_messages_list=recent_messages_list) await self._observe(recent_messages_list=recent_messages_list)
else: else:
# 没有提到继续保持沉默等待5秒防止频繁触发 # 没有提到继续保持沉默等待5秒防止频繁触发
@ -278,7 +282,6 @@ class HeartFChatting:
recent_messages_list = [] recent_messages_list = []
reply_text = "" # 初始化reply_text变量避免UnboundLocalError reply_text = "" # 初始化reply_text变量避免UnboundLocalError
start_time = time.time() start_time = time.time()
if s4u_config.enable_s4u: if s4u_config.enable_s4u:
@ -429,11 +432,6 @@ class HeartFChatting:
else: else:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
"""S4U内容暂时保留""" """S4U内容暂时保留"""
if s4u_config.enable_s4u: if s4u_config.enable_s4u:
await stop_typing() await stop_typing()

View File

@ -1,10 +1,8 @@
import asyncio
import re import re
import traceback import traceback
from typing import Tuple, TYPE_CHECKING from typing import Tuple, TYPE_CHECKING
from src.config.config import global_config
from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.message import MessageRecv
from src.chat.message_receive.storage import MessageStorage from src.chat.message_receive.storage import MessageStorage
from src.chat.heart_flow.heartflow import heartflow from src.chat.heart_flow.heartflow import heartflow
@ -74,7 +72,7 @@ class HeartFCMessageReceiver:
await self.storage.store_message(message, chat) await self.storage.store_message(message, chat)
heartflow_chat: HeartFChatting = await heartflow.get_or_create_heartflow_chat(chat.stream_id) # type: ignore _heartflow_chat: HeartFChatting = await heartflow.get_or_create_heartflow_chat(chat.stream_id) # type: ignore
# 3. 日志记录 # 3. 日志记录
mes_name = chat.group_info.group_name if chat.group_info else "私聊" mes_name = chat.group_info.group_name if chat.group_info else "私聊"
@ -102,7 +100,7 @@ class HeartFCMessageReceiver:
replace_bot_name=True, replace_bot_name=True,
) )
# if not processed_plain_text: # if not processed_plain_text:
# print(message) # print(message)
logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}") # type: ignore logger.info(f"[{mes_name}]{userinfo.user_nickname}:{processed_plain_text}") # type: ignore

View File

@ -8,7 +8,7 @@ from maim_message import UserInfo, Seg, GroupInfo
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import global_config from src.config.config import global_config
from src.mood.mood_manager import mood_manager # 导入情绪管理器 from src.mood.mood_manager import mood_manager # 导入情绪管理器
from src.chat.message_receive.chat_stream import get_chat_manager, ChatStream from src.chat.message_receive.chat_stream import get_chat_manager
from src.chat.message_receive.message import MessageRecv, MessageRecvS4U from src.chat.message_receive.message import MessageRecv, MessageRecvS4U
from src.chat.message_receive.storage import MessageStorage from src.chat.message_receive.storage import MessageStorage
from src.chat.heart_flow.heartflow_message_processor import HeartFCMessageReceiver from src.chat.heart_flow.heartflow_message_processor import HeartFCMessageReceiver

View File

@ -344,7 +344,6 @@ class ActionPlanner:
plan_style=global_config.personality.plan_style, plan_style=global_config.personality.plan_style,
) )
return prompt, message_id_list return prompt, message_id_list
except Exception as e: except Exception as e:
logger.error(f"构建 Planner 提示词时出错: {e}") logger.error(f"构建 Planner 提示词时出错: {e}")
@ -508,9 +507,7 @@ class ActionPlanner:
action.action_data = action.action_data or {} action.action_data = action.action_data or {}
action.action_data["loop_start_time"] = loop_start_time action.action_data["loop_start_time"] = loop_start_time
logger.debug( logger.debug(f"{self.log_prefix}规划器选择了{len(actions)}个动作: {' '.join([a.action_type for a in actions])}")
f"{self.log_prefix}规划器选择了{len(actions)}个动作: {' '.join([a.action_type for a in actions])}"
)
return actions return actions

View File

@ -28,7 +28,7 @@ from src.chat.utils.chat_message_builder import (
from src.chat.express.expression_selector import expression_selector from src.chat.express.expression_selector import expression_selector
# from src.chat.memory_system.memory_activator import MemoryActivator # from src.chat.memory_system.memory_activator import MemoryActivator
from src.person_info.person_info import Person, is_person_known from src.person_info.person_info import Person
from src.plugin_system.base.component_types import ActionInfo, EventType from src.plugin_system.base.component_types import ActionInfo, EventType
from src.plugin_system.apis import llm_api from src.plugin_system.apis import llm_api
@ -43,6 +43,7 @@ init_rewrite_prompt()
logger = get_logger("replyer") logger = get_logger("replyer")
class DefaultReplyer: class DefaultReplyer:
def __init__( def __init__(
self, self,
@ -216,7 +217,7 @@ class DefaultReplyer:
traceback.print_exc() traceback.print_exc()
return False, llm_response return False, llm_response
#移动到 relation插件中构建 # 移动到 relation插件中构建
# async def build_relation_info(self, chat_content: str, sender: str, person_list: List[Person]): # async def build_relation_info(self, chat_content: str, sender: str, person_list: List[Person]):
# if not global_config.relationship.enable_relationship: # if not global_config.relationship.enable_relationship:
# return "" # return ""
@ -278,9 +279,7 @@ class DefaultReplyer:
expression_habits_block = "" expression_habits_block = ""
expression_habits_title = "" expression_habits_title = ""
if style_habits_str.strip(): if style_habits_str.strip():
expression_habits_title = ( expression_habits_title = "在回复时,你可以参考以下的语言习惯,不要生硬使用:"
"在回复时,你可以参考以下的语言习惯,不要生硬使用:"
)
expression_habits_block += f"{style_habits_str}\n" expression_habits_block += f"{style_habits_str}\n"
return f"{expression_habits_title}\n{expression_habits_block}", selected_ids return f"{expression_habits_title}\n{expression_habits_block}", selected_ids
@ -510,7 +509,6 @@ class DefaultReplyer:
-------------------------------- --------------------------------
""" """
# 构建背景对话 prompt # 构建背景对话 prompt
all_dialogue_prompt = "" all_dialogue_prompt = ""
if message_list_before_now: if message_list_before_now:
@ -536,7 +534,6 @@ class DefaultReplyer:
time_block: str, time_block: str,
chat_target_1: str, chat_target_1: str,
chat_target_2: str, chat_target_2: str,
identity_block: str, identity_block: str,
sender: str, sender: str,
target: str, target: str,
@ -774,13 +771,9 @@ class DefaultReplyer:
if sender: if sender:
if is_group_chat: if is_group_chat:
reply_target_block = ( reply_target_block = f"现在{sender}说的:{target}。引起了你的注意"
f"现在{sender}说的:{target}。引起了你的注意"
)
else: # private chat else: # private chat
reply_target_block = ( reply_target_block = f"现在{sender}说的:{target}。引起了你的注意"
f"现在{sender}说的:{target}。引起了你的注意"
)
else: else:
reply_target_block = "" reply_target_block = ""
@ -1061,6 +1054,3 @@ def weighted_sample_no_replacement(items, weights, k) -> list:
pool.pop(idx) pool.pop(idx)
break break
return selected return selected

View File

@ -1,9 +1,7 @@
from src.chat.utils.prompt_builder import Prompt from src.chat.utils.prompt_builder import Prompt
# from src.chat.memory_system.memory_activator import MemoryActivator # from src.chat.memory_system.memory_activator import MemoryActivator
def init_lpmm_prompt(): def init_lpmm_prompt():
Prompt( Prompt(
""" """
@ -20,5 +18,3 @@ If you need to use the search tool, please directly call the function "lpmm_sear
""", """,
name="lpmm_get_knowledge_prompt", name="lpmm_get_knowledge_prompt",
) )

View File

@ -1,9 +1,7 @@
from src.chat.utils.prompt_builder import Prompt from src.chat.utils.prompt_builder import Prompt
# from src.chat.memory_system.memory_activator import MemoryActivator # from src.chat.memory_system.memory_activator import MemoryActivator
def init_rewrite_prompt(): def init_rewrite_prompt():
Prompt("你正在qq群里聊天下面是群里正在聊的内容:", "chat_target_group1") Prompt("你正在qq群里聊天下面是群里正在聊的内容:", "chat_target_group1")
Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1") Prompt("你正在和{sender_name}聊天,这是你们之前聊的内容:", "chat_target_private1")

View File

@ -859,7 +859,6 @@ async def build_anonymous_messages(messages: List[DatabaseMessages]) -> str:
# 处理图片ID # 处理图片ID
content = process_pic_ids(content) content = process_pic_ids(content)
anon_name = get_anon_name(platform, user_id) anon_name = get_anon_name(platform, user_id)
# print(f"anon_name:{anon_name}") # print(f"anon_name:{anon_name}")
@ -945,11 +944,12 @@ async def build_bare_messages(messages: List[DatabaseMessages]) -> str:
# 获取纯文本内容 # 获取纯文本内容
content = msg.processed_plain_text or "" content = msg.processed_plain_text or ""
# 处理图片ID # 处理图片ID
pic_pattern = r"\[picid:[^\]]+\]" pic_pattern = r"\[picid:[^\]]+\]"
def replace_pic_id(match): def replace_pic_id(match):
return "[图片]" return "[图片]"
content = re.sub(pic_pattern, replace_pic_id, content) content = re.sub(pic_pattern, replace_pic_id, content)
# 处理用户引用格式,移除回复和@标记 # 处理用户引用格式,移除回复和@标记

View File

@ -748,11 +748,14 @@ def check_field_constraints():
logger.exception(f"检查字段约束时出错: {e}") logger.exception(f"检查字段约束时出错: {e}")
return inconsistencies return inconsistencies
def fix_image_id(): def fix_image_id():
""" """
修复表情包的 image_id 字段 修复表情包的 image_id 字段
""" """
import uuid import uuid
try: try:
with db: with db:
for img in Images.select(): for img in Images.select():
@ -763,6 +766,7 @@ def fix_image_id():
except Exception as e: except Exception as e:
logger.exception(f"修复 image_id 时出错: {e}") logger.exception(f"修复 image_id 时出错: {e}")
# 模块加载时调用初始化函数 # 模块加载时调用初始化函数
initialize_database(sync_constraints=True) initialize_database(sync_constraints=True)
fix_image_id() fix_image_id()

View File

@ -302,6 +302,7 @@ class EmojiConfig(ConfigBase):
filtration_prompt: str = "符合公序良俗" filtration_prompt: str = "符合公序良俗"
"""表情包过滤要求""" """表情包过滤要求"""
@dataclass @dataclass
class KeywordRuleConfig(ConfigBase): class KeywordRuleConfig(ConfigBase):
"""关键词规则配置类""" """关键词规则配置类"""

View File

@ -250,10 +250,7 @@ def _build_stream_api_resp(
" 可能会对回复内容造成影响,建议修改模型 max_tokens 配置!" " 可能会对回复内容造成影响,建议修改模型 max_tokens 配置!"
) )
else: else:
logger.warning( logger.warning("⚠ Gemini 响应因达到 max_tokens 限制被截断,\n 请修改模型 max_tokens 配置!")
"⚠ Gemini 响应因达到 max_tokens 限制被截断,\n"
" 请修改模型 max_tokens 配置!"
)
if not resp.content and not resp.tool_calls: if not resp.content and not resp.tool_calls:
raise EmptyResponseException() raise EmptyResponseException()
@ -387,10 +384,7 @@ def _default_normal_response_parser(
" 可能会对回复内容造成影响,建议修改模型 max_tokens 配置!" " 可能会对回复内容造成影响,建议修改模型 max_tokens 配置!"
) )
else: else:
logger.warning( logger.warning("⚠ Gemini 响应因达到 max_tokens 限制被截断,\n 请修改模型 max_tokens 配置!")
"⚠ Gemini 响应因达到 max_tokens 限制被截断,\n"
" 请修改模型 max_tokens 配置!"
)
return api_response, _usage_record return api_response, _usage_record
except Exception as e: except Exception as e:

View File

@ -103,9 +103,7 @@ class ChatMood:
if random.random() > update_probability: if random.random() > update_probability:
return return
logger.debug( logger.debug(f"{self.log_prefix} 更新情绪状态,更新概率: {update_probability:.2f}")
f"{self.log_prefix} 更新情绪状态,更新概率: {update_probability:.2f}"
)
message_time: float = message.message_info.time # type: ignore message_time: float = message.message_info.time # type: ignore
message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive( message_list_before_now = get_raw_msg_by_timestamp_with_chat_inclusive(

View File

@ -17,7 +17,9 @@ from src.config.config import global_config, model_config
logger = get_logger("person_info") logger = get_logger("person_info")
relation_selection_model = LLMRequest(model_set=model_config.model_task_config.utils_small, request_type="relation_selection") relation_selection_model = LLMRequest(
model_set=model_config.model_task_config.utils_small, request_type="relation_selection"
)
def get_person_id(platform: str, user_id: Union[int, str]) -> str: def get_person_id(platform: str, user_id: Union[int, str]) -> str:
@ -93,7 +95,8 @@ def extract_categories_from_response(response: str) -> list[str]:
return [] return []
import re import re
pattern = r'<([^<>]+)>'
pattern = r"<([^<>]+)>"
matches = re.findall(pattern, response) matches = re.findall(pattern, response)
return matches return matches
@ -420,7 +423,7 @@ class Person:
except Exception as e: except Exception as e:
logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}") logger.error(f"同步用户 {self.person_id} 信息到数据库时出错: {e}")
async def build_relationship(self,chat_content:str = "",info_type = ""): async def build_relationship(self, chat_content: str = "", info_type=""):
if not self.is_known: if not self.is_known:
return "" return ""
# 构建points文本 # 构建points文本
@ -449,11 +452,13 @@ class Person:
# print(prompt) # print(prompt)
# print(response) # print(response)
category_list = extract_categories_from_response(response) category_list = extract_categories_from_response(response)
if "none" not in category_list: if "none" not in category_list:
for category in category_list: for category in category_list:
random_memory = self.get_random_memory_by_category(category, 2) random_memory = self.get_random_memory_by_category(category, 2)
if random_memory: if random_memory:
random_memory_str = "\n".join([get_memory_content_from_memory(memory) for memory in random_memory]) random_memory_str = "\n".join(
[get_memory_content_from_memory(memory) for memory in random_memory]
)
points_text = f"有关 {category} 的内容:{random_memory_str}" points_text = f"有关 {category} 的内容:{random_memory_str}"
break break
elif info_type: elif info_type:
@ -469,15 +474,16 @@ class Person:
# print(prompt) # print(prompt)
# print(response) # print(response)
category_list = extract_categories_from_response(response) category_list = extract_categories_from_response(response)
if "none" not in category_list: if "none" not in category_list:
for category in category_list: for category in category_list:
random_memory = self.get_random_memory_by_category(category, 3) random_memory = self.get_random_memory_by_category(category, 3)
if random_memory: if random_memory:
random_memory_str = "\n".join([get_memory_content_from_memory(memory) for memory in random_memory]) random_memory_str = "\n".join(
[get_memory_content_from_memory(memory) for memory in random_memory]
)
points_text = f"有关 {category} 的内容:{random_memory_str}" points_text = f"有关 {category} 的内容:{random_memory_str}"
break break
else: else:
for category in category_list: for category in category_list:
random_memory = self.get_random_memory_by_category(category, 1)[0] random_memory = self.get_random_memory_by_category(category, 1)[0]
if random_memory: if random_memory:

View File

@ -12,7 +12,6 @@ import random
import base64 import base64
import os import os
import uuid import uuid
import time
from typing import Optional, Tuple, List, Dict, Any from typing import Optional, Tuple, List, Dict, Any
from src.common.logger import get_logger from src.common.logger import get_logger
@ -358,7 +357,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": None, "description": None,
"emotions": None, "emotions": None,
"replaced": None, "replaced": None,
"hash": None "hash": None,
} }
# 3. 确保emoji目录存在 # 3. 确保emoji目录存在
@ -368,19 +367,21 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
if not filename: if not filename:
# 基于时间戳、微秒和短base64生成唯一文件名 # 基于时间戳、微秒和短base64生成唯一文件名
import time import time
timestamp = int(time.time()) timestamp = int(time.time())
microseconds = int(time.time() * 1000000) % 1000000 # 添加微秒级精度 microseconds = int(time.time() * 1000000) % 1000000 # 添加微秒级精度
# 生成12位随机标识符使用base64编码增加随机性 # 生成12位随机标识符使用base64编码增加随机性
import random import random
random_bytes = random.getrandbits(72).to_bytes(9, 'big') # 72位 = 9字节 = 12位base64
short_id = base64.b64encode(random_bytes).decode('ascii')[:12].rstrip('=') random_bytes = random.getrandbits(72).to_bytes(9, "big") # 72位 = 9字节 = 12位base64
short_id = base64.b64encode(random_bytes).decode("ascii")[:12].rstrip("=")
# 确保base64编码适合文件名替换/和- # 确保base64编码适合文件名替换/和-
short_id = short_id.replace('/', '_').replace('+', '-') short_id = short_id.replace("/", "_").replace("+", "-")
filename = f"emoji_{timestamp}_{microseconds}_{short_id}" filename = f"emoji_{timestamp}_{microseconds}_{short_id}"
# 确保文件名有扩展名 # 确保文件名有扩展名
if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif')): if not filename.lower().endswith((".jpg", ".jpeg", ".png", ".gif")):
filename = f"{filename}.png" # 默认使用png格式 filename = f"{filename}.png" # 默认使用png格式
# 检查文件名是否已存在,如果存在则重新生成短标识符 # 检查文件名是否已存在,如果存在则重新生成短标识符
@ -390,14 +391,15 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
while os.path.exists(temp_file_path) and attempts < max_attempts: while os.path.exists(temp_file_path) and attempts < max_attempts:
# 重新生成短标识符 # 重新生成短标识符
import random import random
random_bytes = random.getrandbits(48).to_bytes(6, 'big')
short_id = base64.b64encode(random_bytes).decode('ascii')[:8].rstrip('=') random_bytes = random.getrandbits(48).to_bytes(6, "big")
short_id = short_id.replace('/', '_').replace('+', '-') short_id = base64.b64encode(random_bytes).decode("ascii")[:8].rstrip("=")
short_id = short_id.replace("/", "_").replace("+", "-")
# 分离文件名和扩展名,重新生成文件名 # 分离文件名和扩展名,重新生成文件名
name_part, ext = os.path.splitext(filename) name_part, ext = os.path.splitext(filename)
# 去掉原来的标识符,添加新的 # 去掉原来的标识符,添加新的
base_name = name_part.rsplit('_', 1)[0] # 移除最后一个_后的部分 base_name = name_part.rsplit("_", 1)[0] # 移除最后一个_后的部分
filename = f"{base_name}_{short_id}{ext}" filename = f"{base_name}_{short_id}{ext}"
temp_file_path = os.path.join(EMOJI_DIR, filename) temp_file_path = os.path.join(EMOJI_DIR, filename)
attempts += 1 attempts += 1
@ -406,7 +408,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
if os.path.exists(temp_file_path): if os.path.exists(temp_file_path):
uuid_short = str(uuid.uuid4())[:8] uuid_short = str(uuid.uuid4())[:8]
name_part, ext = os.path.splitext(filename) name_part, ext = os.path.splitext(filename)
base_name = name_part.rsplit('_', 1)[0] base_name = name_part.rsplit("_", 1)[0]
filename = f"{base_name}_{uuid_short}{ext}" filename = f"{base_name}_{uuid_short}{ext}"
temp_file_path = os.path.join(EMOJI_DIR, filename) temp_file_path = os.path.join(EMOJI_DIR, filename)
@ -428,7 +430,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": None, "description": None,
"emotions": None, "emotions": None,
"replaced": None, "replaced": None,
"hash": None "hash": None,
} }
# 5. 保存base64图片到emoji目录 # 5. 保存base64图片到emoji目录
@ -443,7 +445,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": None, "description": None,
"emotions": None, "emotions": None,
"replaced": None, "replaced": None,
"hash": None "hash": None,
} }
logger.debug(f"[EmojiAPI] 图片已保存到临时文件: {temp_file_path}") logger.debug(f"[EmojiAPI] 图片已保存到临时文件: {temp_file_path}")
@ -456,7 +458,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": None, "description": None,
"emotions": None, "emotions": None,
"replaced": None, "replaced": None,
"hash": None "hash": None,
} }
# 6. 调用注册方法 # 6. 调用注册方法
@ -483,8 +485,8 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
# 通过文件名查找新注册的表情包(注意:文件名在注册后可能已经改变) # 通过文件名查找新注册的表情包(注意:文件名在注册后可能已经改变)
for emoji_obj in reversed(emoji_manager.emoji_objects): for emoji_obj in reversed(emoji_manager.emoji_objects):
if not emoji_obj.is_deleted and ( if not emoji_obj.is_deleted and (
emoji_obj.filename == filename or # 直接匹配 emoji_obj.filename == filename # 直接匹配
(hasattr(emoji_obj, 'full_path') and filename in emoji_obj.full_path) # 路径包含匹配 or (hasattr(emoji_obj, "full_path") and filename in emoji_obj.full_path) # 路径包含匹配
): ):
new_emoji_info = emoji_obj new_emoji_info = emoji_obj
break break
@ -501,7 +503,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": description, "description": description,
"emotions": emotions, "emotions": emotions,
"replaced": replaced, "replaced": replaced,
"hash": emoji_hash "hash": emoji_hash,
} }
else: else:
return { return {
@ -510,7 +512,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": None, "description": None,
"emotions": None, "emotions": None,
"replaced": None, "replaced": None,
"hash": None "hash": None,
} }
except Exception as e: except Exception as e:
@ -521,7 +523,7 @@ async def register_emoji(image_base64: str, filename: Optional[str] = None) -> D
"description": None, "description": None,
"emotions": None, "emotions": None,
"replaced": None, "replaced": None,
"hash": None "hash": None,
} }
@ -585,16 +587,16 @@ async def delete_emoji(emoji_hash: str) -> Dict[str, Any]:
"count_before": count_before, "count_before": count_before,
"count_after": count_after, "count_after": count_after,
"description": description, "description": description,
"emotions": emotions "emotions": emotions,
} }
else: else:
return { return {
"success": False, "success": False,
"message": f"表情包删除失败,可能因为哈希值不存在或删除过程出错", "message": "表情包删除失败,可能因为哈希值不存在或删除过程出错",
"count_before": count_before, "count_before": count_before,
"count_after": count_after, "count_after": count_after,
"description": None, "description": None,
"emotions": None "emotions": None,
} }
except Exception as e: except Exception as e:
@ -605,7 +607,7 @@ async def delete_emoji(emoji_hash: str) -> Dict[str, Any]:
"count_before": None, "count_before": None,
"count_after": None, "count_after": None,
"description": None, "description": None,
"emotions": None "emotions": None,
} }
@ -659,7 +661,7 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals
"message": f"未找到匹配描述 '{description}' 的表情包", "message": f"未找到匹配描述 '{description}' 的表情包",
"deleted_count": 0, "deleted_count": 0,
"deleted_hashes": [], "deleted_hashes": [],
"matched_count": 0 "matched_count": 0,
} }
# 删除匹配的表情包 # 删除匹配的表情包
@ -681,7 +683,7 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals
"message": f"成功删除 {deleted_count} 个表情包 (匹配到 {matched_count} 个)", "message": f"成功删除 {deleted_count} 个表情包 (匹配到 {matched_count} 个)",
"deleted_count": deleted_count, "deleted_count": deleted_count,
"deleted_hashes": deleted_hashes, "deleted_hashes": deleted_hashes,
"matched_count": matched_count "matched_count": matched_count,
} }
else: else:
return { return {
@ -689,7 +691,7 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals
"message": f"匹配到 {matched_count} 个表情包,但删除全部失败", "message": f"匹配到 {matched_count} 个表情包,但删除全部失败",
"deleted_count": 0, "deleted_count": 0,
"deleted_hashes": [], "deleted_hashes": [],
"matched_count": matched_count "matched_count": matched_count,
} }
except Exception as e: except Exception as e:
@ -699,5 +701,5 @@ async def delete_emoji_by_description(description: str, exact_match: bool = Fals
"message": f"删除过程中发生错误: {str(e)}", "message": f"删除过程中发生错误: {str(e)}",
"deleted_count": 0, "deleted_count": 0,
"deleted_hashes": [], "deleted_hashes": [],
"matched_count": 0 "matched_count": 0,
} }

View File

@ -3,13 +3,14 @@ from src.chat.frequency_control.frequency_control import frequency_control_manag
logger = get_logger("frequency_api") logger = get_logger("frequency_api")
def get_current_talk_frequency(chat_id: str) -> float: def get_current_talk_frequency(chat_id: str) -> float:
return frequency_control_manager.get_or_create_frequency_control(chat_id).get_talk_frequency_adjust() return frequency_control_manager.get_or_create_frequency_control(chat_id).get_talk_frequency_adjust()
def set_talk_frequency_adjust(chat_id: str, talk_frequency_adjust: float) -> None: def set_talk_frequency_adjust(chat_id: str, talk_frequency_adjust: float) -> None:
frequency_control_manager.get_or_create_frequency_control( frequency_control_manager.get_or_create_frequency_control(chat_id).set_talk_frequency_adjust(talk_frequency_adjust)
chat_id
).set_talk_frequency_adjust(talk_frequency_adjust)
def get_talk_frequency_adjust(chat_id: str) -> float: def get_talk_frequency_adjust(chat_id: str) -> float:
return frequency_control_manager.get_or_create_frequency_control(chat_id).get_talk_frequency_adjust() return frequency_control_manager.get_or_create_frequency_control(chat_id).get_talk_frequency_adjust()

View File

@ -1,9 +1,6 @@
import asyncio import asyncio
import traceback from typing import Optional
import time
from typing import Optional, Union, Dict, List, TYPE_CHECKING, Tuple
from src.chat.message_receive import message
from src.common.logger import get_logger from src.common.logger import get_logger
from src.mood.mood_manager import mood_manager from src.mood.mood_manager import mood_manager

View File

@ -451,7 +451,9 @@ def _parse_content_to_seg(reply_content: "ReplyContent") -> Tuple[Seg, bool]:
single_node_content.append(sub_seg) single_node_content.append(sub_seg)
message_segment = Seg(type="seglist", data=single_node_content) message_segment = Seg(type="seglist", data=single_node_content)
forward_message_list.append( forward_message_list.append(
MessageBase(message_segment=message_segment, message_info=BaseMessageInfo(user_info=user_info)).to_dict() MessageBase(
message_segment=message_segment, message_info=BaseMessageInfo(user_info=user_info)
).to_dict()
) )
return Seg(type="forward", data=forward_message_list), False # type: ignore return Seg(type="forward", data=forward_message_list), False # type: ignore
else: else:

View File

@ -14,7 +14,6 @@ from src.plugins.built_in.relation.relation import BuildRelationAction
logger = get_logger("relation_actions") logger = get_logger("relation_actions")
class GetPersonInfoTool(BaseTool): class GetPersonInfoTool(BaseTool):
"""获取用户信息""" """获取用户信息"""