refc:重构插件api,补全文档,合并expressor和replyer,分离reply和sender,新log浏览器

This commit is contained in:
SengokuCola
2025-06-19 20:20:34 +08:00
parent 7e05ede846
commit ab28b94e33
63 changed files with 5285 additions and 8316 deletions

View File

@@ -1,534 +0,0 @@
import traceback
from typing import List, Optional, Dict, Any, Tuple
from src.chat.focus_chat.expressors.exprssion_learner import get_expression_learner
from src.chat.message_receive.message import MessageRecv, MessageThinking, MessageSending
from src.chat.message_receive.message import Seg # Local import needed after move
from src.chat.message_receive.message import UserInfo
from src.chat.message_receive.chat_stream import get_chat_manager
from src.common.logger import get_logger
from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config
from src.chat.utils.utils_image import image_path_to_base64 # Local import needed after move
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
from src.chat.emoji_system.emoji_manager import get_emoji_manager
from src.chat.focus_chat.heartFC_sender import HeartFCSender
from src.chat.utils.utils import process_llm_response
from src.chat.heart_flow.utils_chat import get_chat_type_and_target_info
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.focus_chat.hfc_utils import parse_thinking_id_to_timestamp
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
from src.chat.utils.chat_message_builder import build_readable_messages, get_raw_msg_before_timestamp_with_chat
import time
import random
logger = get_logger("expressor")
def init_prompt():
Prompt(
"""
你可以参考你的以下的语言习惯,如果情景合适就使用,不要盲目使用,不要生硬使用,而是结合到表达中:
{style_habbits}
你现在正在群里聊天,以下是群里正在进行的聊天内容:
{chat_info}
以上是聊天内容,你需要了解聊天记录中的内容
{chat_target}
你的名字是{bot_name}{prompt_personality},在这聊天中,"{target_message}"引起了你的注意,对这句话,你想表达:{in_mind_reply},原因是:{reason}。你现在要思考怎么回复
你需要使用合适的语法和句法,参考聊天内容,组织一条日常且口语化的回复。请你修改你想表达的原句,符合你的表达风格和语言习惯
请你根据情景使用以下句法:
{grammar_habbits}
{config_expression_style},你可以完全重组回复,保留最基本的表达含义就好,但重组后保持语意通顺。
不要浮夸,不要夸张修辞,平淡且不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 ),只输出一条回复就好。
现在,你说:
""",
"default_expressor_prompt",
)
Prompt(
"""
你可以参考以下的语言习惯,如果情景合适就使用,不要盲目使用,不要生硬使用,而是结合到表达中:
{style_habbits}
你现在正在群里聊天,以下是群里正在进行的聊天内容:
{chat_info}
以上是聊天内容,你需要了解聊天记录中的内容
{chat_target}
你的名字是{bot_name}{prompt_personality},在这聊天中,"{target_message}"引起了你的注意,对这句话,你想表达:{in_mind_reply},原因是:{reason}。你现在要思考怎么回复
你需要使用合适的语法和句法,参考聊天内容,组织一条日常且口语化的回复。
请你根据情景使用以下句法:
{grammar_habbits}
{config_expression_style},你可以完全重组回复,保留最基本的表达含义就好,但重组后保持语意通顺。
不要浮夸,不要夸张修辞,平淡且不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 ),只输出一条回复就好。
现在,你说:
""",
"default_expressor_private_prompt", # New template for private FOCUSED chat
)
class DefaultExpressor:
def __init__(self, chat_stream: ChatStream):
self.log_prefix = "expressor"
# TODO: API-Adapter修改标记
self.express_model = LLMRequest(
model=global_config.model.replyer_1,
request_type="focus.expressor",
)
self.heart_fc_sender = HeartFCSender()
self.chat_id = chat_stream.stream_id
self.chat_stream = chat_stream
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id)
async def _create_thinking_message(self, anchor_message: Optional[MessageRecv], thinking_id: str):
"""创建思考消息 (尝试锚定到 anchor_message)"""
if not anchor_message or not anchor_message.chat_stream:
logger.error(f"{self.log_prefix} 无法创建思考消息,缺少有效的锚点消息或聊天流。")
return None
chat = anchor_message.chat_stream
messageinfo = anchor_message.message_info
thinking_time_point = parse_thinking_id_to_timestamp(thinking_id)
bot_user_info = UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=messageinfo.platform,
)
thinking_message = MessageThinking(
message_id=thinking_id,
chat_stream=chat,
bot_user_info=bot_user_info,
reply=anchor_message, # 回复的是锚点消息
thinking_start_time=thinking_time_point,
)
# logger.debug(f"创建思考消息thinking_message{thinking_message}")
await self.heart_fc_sender.register_thinking(thinking_message)
return None
async def deal_reply(
self,
cycle_timers: dict,
action_data: Dict[str, Any],
reasoning: str,
anchor_message: MessageRecv,
thinking_id: str,
) -> tuple[bool, Optional[List[Tuple[str, str]]]]:
# 创建思考消息
await self._create_thinking_message(anchor_message, thinking_id)
reply = [] # 初始化 reply防止未定义
try:
has_sent_something = False
# 处理文本部分
text_part = action_data.get("text", [])
if text_part:
with Timer("生成回复", cycle_timers):
# 可以保留原有的文本处理逻辑或进行适当调整
reply = await self.express(
in_mind_reply=text_part,
anchor_message=anchor_message,
thinking_id=thinking_id,
reason=reasoning,
action_data=action_data,
)
with Timer("选择表情", cycle_timers):
emoji_keyword = action_data.get("emojis", [])
emoji_base64 = await self._choose_emoji(emoji_keyword)
if emoji_base64:
reply.append(("emoji", emoji_base64))
if reply:
with Timer("发送消息", cycle_timers):
sent_msg_list = await self.send_response_messages(
anchor_message=anchor_message,
thinking_id=thinking_id,
response_set=reply,
)
has_sent_something = True
else:
logger.warning(f"{self.log_prefix} 文本回复生成失败")
if not has_sent_something:
logger.warning(f"{self.log_prefix} 回复动作未包含任何有效内容")
return has_sent_something, sent_msg_list
except Exception as e:
logger.error(f"回复失败: {e}")
traceback.print_exc()
return False, None
# --- 回复器 (Replier) 的定义 --- #
async def express(
self,
in_mind_reply: str,
reason: str,
anchor_message: MessageRecv,
thinking_id: str,
action_data: Dict[str, Any],
) -> Optional[List[str]]:
"""
回复器 (Replier): 核心逻辑,负责生成回复文本。
(已整合原 HeartFCGenerator 的功能)
"""
try:
# --- Determine sender_name for private chat ---
sender_name_for_prompt = "某人" # Default for group or if info unavailable
if not self.is_group_chat and self.chat_target_info:
# Prioritize person_name, then nickname
sender_name_for_prompt = (
self.chat_target_info.get("person_name")
or self.chat_target_info.get("user_nickname")
or sender_name_for_prompt
)
# --- End determining sender_name ---
target_message = action_data.get("target", "")
# 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_focus(
chat_stream=self.chat_stream, # Pass the stream object
in_mind_reply=in_mind_reply,
reason=reason,
sender_name=sender_name_for_prompt, # Pass determined name
target_message=target_message,
config_expression_style=global_config.expression.expression_style,
)
# 4. 调用 LLM 生成回复
content = None
reasoning_content = None
model_name = "unknown_model"
if not prompt:
logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Prompt 构建失败,无法生成回复。")
return None
try:
with Timer("LLM生成", {}): # 内部计时器,可选保留
# TODO: API-Adapter修改标记
# logger.info(f"{self.log_prefix}[Replier-{thinking_id}]\nPrompt:\n{prompt}\n")
content, (reasoning_content, model_name) = await self.express_model.generate_response_async(prompt)
logger.info(f"想要表达:{in_mind_reply}||理由:{reason}")
logger.info(f"最终回复: {content}\n")
except Exception as llm_e:
# 精简报错信息
logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}")
return None # LLM 调用失败则无法生成回复
processed_response = process_llm_response(content)
# 5. 处理 LLM 响应
if not content:
logger.warning(f"{self.log_prefix}LLM 生成了空内容。")
return None
if not processed_response:
logger.warning(f"{self.log_prefix}处理后的回复为空。")
return None
reply_set = []
for str in processed_response:
reply_seg = ("text", str)
reply_set.append(reply_seg)
return reply_set
except Exception as e:
logger.error(f"{self.log_prefix}回复生成意外失败: {e}")
traceback.print_exc()
return None
async def build_prompt_focus(
self,
reason,
chat_stream,
sender_name,
in_mind_reply,
target_message,
config_expression_style,
) -> str:
is_group_chat = bool(chat_stream.group_info)
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id,
timestamp=time.time(),
limit=global_config.focus_chat.observation_context_size,
)
chat_talking_prompt = build_readable_messages(
message_list_before_now,
replace_bot_name=True,
merge_messages=True,
timestamp_mode="relative",
read_mark=0.0,
truncate=True,
)
expression_learner = get_expression_learner()
(
learnt_style_expressions,
learnt_grammar_expressions,
personality_expressions,
) = await expression_learner.get_expression_by_chat_id(chat_stream.stream_id)
style_habbits = []
grammar_habbits = []
# 1. learnt_expressions加权随机选3条
if learnt_style_expressions:
weights = [expr["count"] for expr in learnt_style_expressions]
selected_learnt = weighted_sample_no_replacement(learnt_style_expressions, weights, 3)
for expr in selected_learnt:
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
style_habbits.append(f"{expr['situation']}时,使用 {expr['style']}")
# 2. learnt_grammar_expressions加权随机选3条
if learnt_grammar_expressions:
weights = [expr["count"] for expr in learnt_grammar_expressions]
selected_learnt = weighted_sample_no_replacement(learnt_grammar_expressions, weights, 3)
for expr in selected_learnt:
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
grammar_habbits.append(f"{expr['situation']}时,使用 {expr['style']}")
# 3. personality_expressions随机选1条
if personality_expressions:
expr = random.choice(personality_expressions)
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
style_habbits.append(f"{expr['situation']}时,使用 {expr['style']}")
style_habbits_str = "\n".join(style_habbits)
grammar_habbits_str = "\n".join(grammar_habbits)
logger.debug("开始构建 focus prompt")
# --- Choose template based on chat type ---
if is_group_chat:
template_name = "default_expressor_prompt"
# Group specific formatting variables (already fetched or default)
chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1")
# chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
prompt = await global_prompt_manager.format_prompt(
template_name,
style_habbits=style_habbits_str,
grammar_habbits=grammar_habbits_str,
chat_target=chat_target_1,
chat_info=chat_talking_prompt,
bot_name=global_config.bot.nickname,
prompt_personality="",
reason=reason,
in_mind_reply=in_mind_reply,
target_message=target_message,
config_expression_style=config_expression_style,
)
else: # Private chat
template_name = "default_expressor_private_prompt"
chat_target_1 = "你正在和人私聊"
prompt = await global_prompt_manager.format_prompt(
template_name,
style_habbits=style_habbits_str,
grammar_habbits=grammar_habbits_str,
chat_target=chat_target_1,
chat_info=chat_talking_prompt,
bot_name=global_config.bot.nickname,
prompt_personality="",
reason=reason,
in_mind_reply=in_mind_reply,
target_message=target_message,
config_expression_style=config_expression_style,
)
return prompt
# --- 发送器 (Sender) --- #
async def send_response_messages(
self,
anchor_message: Optional[MessageRecv],
response_set: List[Tuple[str, str]],
thinking_id: str = "",
display_message: str = "",
) -> Optional[MessageSending]:
"""发送回复消息 (尝试锚定到 anchor_message),使用 HeartFCSender"""
chat = self.chat_stream
chat_id = self.chat_id
if chat is None:
logger.error(f"{self.log_prefix} 无法发送回复chat_stream 为空。")
return None
if not anchor_message:
logger.error(f"{self.log_prefix} 无法发送回复anchor_message 为空。")
return None
stream_name = get_chat_manager().get_stream_name(chat_id) or chat_id # 获取流名称用于日志
# 检查思考过程是否仍在进行,并获取开始时间
if thinking_id:
thinking_start_time = await self.heart_fc_sender.get_thinking_start_time(chat_id, thinking_id)
else:
thinking_id = "ds" + str(round(time.time(), 2))
thinking_start_time = time.time()
if thinking_start_time is None:
logger.error(f"[{stream_name}]expressor思考过程未找到或已结束无法发送回复。")
return None
mark_head = False
# first_bot_msg: Optional[MessageSending] = None
reply_message_ids = [] # 记录实际发送的消息ID
sent_msg_list = []
for i, msg_text in enumerate(response_set):
# 为每个消息片段生成唯一ID
type = msg_text[0]
data = msg_text[1]
if global_config.experimental.debug_show_chat_mode and type == "text":
data += ""
part_message_id = f"{thinking_id}_{i}"
message_segment = Seg(type=type, data=data)
if type == "emoji":
is_emoji = True
else:
is_emoji = False
reply_to = not mark_head
bot_message = await self._build_single_sending_message(
anchor_message=anchor_message,
message_id=part_message_id,
message_segment=message_segment,
display_message=display_message,
reply_to=reply_to,
is_emoji=is_emoji,
thinking_id=thinking_id,
thinking_start_time=thinking_start_time,
)
try:
if not mark_head:
mark_head = True
# first_bot_msg = bot_message # 保存第一个成功发送的消息对象
typing = False
else:
typing = True
if type == "emoji":
typing = False
if anchor_message.raw_message:
set_reply = True
else:
set_reply = False
sent_msg = await self.heart_fc_sender.send_message(
bot_message, has_thinking=True, typing=typing, set_reply=set_reply
)
reply_message_ids.append(part_message_id) # 记录我们生成的ID
sent_msg_list.append((type, sent_msg))
except Exception as e:
logger.error(f"{self.log_prefix}发送回复片段 {i} ({part_message_id}) 时失败: {e}")
traceback.print_exc()
# 这里可以选择是继续发送下一个片段还是中止
# 在尝试发送完所有片段后,完成原始的 thinking_id 状态
try:
await self.heart_fc_sender.complete_thinking(chat_id, thinking_id)
except Exception as e:
logger.error(f"{self.log_prefix}完成思考状态 {thinking_id} 时出错: {e}")
return sent_msg_list
async def _choose_emoji(self, send_emoji: str):
"""
选择表情根据send_emoji文本选择表情返回表情base64
"""
emoji_base64 = ""
emoji_raw = await get_emoji_manager().get_emoji_for_text(send_emoji)
if emoji_raw:
emoji_path, _description, _emotion = emoji_raw
emoji_base64 = image_path_to_base64(emoji_path)
return emoji_base64
async def _build_single_sending_message(
self,
anchor_message: MessageRecv,
message_id: str,
message_segment: Seg,
reply_to: bool,
is_emoji: bool,
thinking_id: str,
thinking_start_time: float,
display_message: str,
) -> MessageSending:
"""构建单个发送消息"""
bot_user_info = UserInfo(
user_id=global_config.bot.qq_account,
user_nickname=global_config.bot.nickname,
platform=self.chat_stream.platform,
)
bot_message = MessageSending(
message_id=message_id, # 使用片段的唯一ID
chat_stream=self.chat_stream,
bot_user_info=bot_user_info,
sender_info=anchor_message.message_info.user_info,
message_segment=message_segment,
reply=anchor_message, # 回复原始锚点
is_head=reply_to,
is_emoji=is_emoji,
thinking_start_time=thinking_start_time, # 传递原始思考开始时间
display_message=display_message,
)
return bot_message
def weighted_sample_no_replacement(items, weights, k) -> list:
"""
加权且不放回地随机抽取k个元素。
参数:
items: 待抽取的元素列表
weights: 每个元素对应的权重与items等长且为正数
k: 需要抽取的元素个数
返回:
selected: 按权重加权且不重复抽取的k个元素组成的列表
如果items中的元素不足k就只会返回所有可用的元素
实现思路:
每次从当前池中按权重加权随机选出一个元素选中后将其从池中移除重复k次。
这样保证了:
1. count越大被选中概率越高
2. 不会重复选中同一个元素
"""
selected = []
pool = list(zip(items, weights))
for _ in range(min(k, len(pool))):
total = sum(w for _, w in pool)
r = random.uniform(0, total)
upto = 0
for idx, (item, weight) in enumerate(pool):
upto += weight
if upto >= r:
selected.append(item)
pool.pop(idx)
break
return selected
init_prompt()

View File

@@ -1,409 +0,0 @@
import time
import random
from typing import List, Dict, Optional, Any, Tuple
from src.common.logger import get_logger
from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config
from src.chat.utils.chat_message_builder import get_raw_msg_by_timestamp_random, build_anonymous_messages
from src.chat.utils.prompt_builder import Prompt, global_prompt_manager
import os
from src.chat.message_receive.chat_stream import get_chat_manager
import json
MAX_EXPRESSION_COUNT = 300
DECAY_DAYS = 30 # 30天衰减到0.01
DECAY_MIN = 0.01 # 最小衰减值
logger = get_logger("expressor")
def init_prompt() -> None:
learn_style_prompt = """
{chat_str}
请从上面这段群聊中概括除了人名为"SELF"之外的人的语言风格
1. 只考虑文字,不要考虑表情包和图片
2. 不要涉及具体的人名,只考虑语言风格
3. 语言风格包含特殊内容和情感
4. 思考有没有特殊的梗,一并总结成语言风格
5. 例子仅供参考,请严格根据群聊内容总结!!!
注意:总结成如下格式的规律,总结的内容要详细,但具有概括性:
"xxxxxx"时,可以"xxxxxx", xxxxxx不超过20个字
例如:
"对某件事表示十分惊叹,有些意外"时,使用"我嘞个xxxx"
"表示讽刺的赞同,不想讲道理"时,使用"对对对"
"想说明某个具体的事实观点,但懒得明说,或者不便明说,或表达一种默契",使用"懂的都懂"
"当涉及游戏相关时,表示意外的夸赞,略带戏谑意味"时,使用"这么强!"
注意不要总结你自己SELF的发言
现在请你概括
"""
Prompt(learn_style_prompt, "learn_style_prompt")
learn_grammar_prompt = """
{chat_str}
请从上面这段群聊中概括除了人名为"SELF"之外的人的语法和句法特点,只考虑纯文字,不要考虑表情包和图片
1.不要总结【图片】,【动画表情】,[图片][动画表情],不总结 表情符号 at @ 回复 和[回复]
2.不要涉及具体的人名,只考虑语法和句法特点,
3.语法和句法特点要包括,句子长短(具体字数),有何种语病,如何拆分句子。
4. 例子仅供参考,请严格根据群聊内容总结!!!
总结成如下格式的规律,总结的内容要简洁,不浮夸:
"xxx"时,可以"xxx"
例如:
"表达观点较复杂"时,使用"省略主语(3-6个字)"的句法
"不用详细说明的一般表达"时,使用"非常简洁的句子"的句法
"需要单纯简单的确认"时,使用"单字或几个字的肯定(1-2个字)"的句法
注意不要总结你自己SELF的发言
现在请你概括
"""
Prompt(learn_grammar_prompt, "learn_grammar_prompt")
class ExpressionLearner:
def __init__(self) -> None:
# TODO: API-Adapter修改标记
self.express_learn_model: LLMRequest = LLMRequest(
model=global_config.model.replyer_1,
temperature=0.1,
request_type="expressor.learner",
)
async def get_expression_by_chat_id(self, chat_id: str) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
"""
读取/data/expression/learnt/{chat_id}/expressions.json和/data/expression/personality/expressions.json
返回(learnt_expressions, personality_expressions)
"""
learnt_style_file = os.path.join("data", "expression", "learnt_style", str(chat_id), "expressions.json")
learnt_grammar_file = os.path.join("data", "expression", "learnt_grammar", str(chat_id), "expressions.json")
personality_file = os.path.join("data", "expression", "personality", "expressions.json")
learnt_style_expressions = []
learnt_grammar_expressions = []
personality_expressions = []
if os.path.exists(learnt_style_file):
with open(learnt_style_file, "r", encoding="utf-8") as f:
learnt_style_expressions = json.load(f)
if os.path.exists(learnt_grammar_file):
with open(learnt_grammar_file, "r", encoding="utf-8") as f:
learnt_grammar_expressions = json.load(f)
if os.path.exists(personality_file):
with open(personality_file, "r", encoding="utf-8") as f:
personality_expressions = json.load(f)
return learnt_style_expressions, learnt_grammar_expressions, personality_expressions
def is_similar(self, s1: str, s2: str) -> bool:
"""
判断两个字符串是否相似只考虑长度大于5且有80%以上重合,不考虑子串)
"""
if not s1 or not s2:
return False
min_len = min(len(s1), len(s2))
if min_len < 5:
return False
same = sum(1 for a, b in zip(s1, s2) if a == b)
return same / min_len > 0.8
async def learn_and_store_expression(self) -> List[Tuple[str, str, str]]:
"""
学习并存储表达方式,分别学习语言风格和句法特点
同时对所有已存储的表达方式进行全局衰减
"""
current_time = time.time()
# 全局衰减所有已存储的表达方式
for type in ["style", "grammar"]:
base_dir = os.path.join("data", "expression", f"learnt_{type}")
if not os.path.exists(base_dir):
continue
for chat_id in os.listdir(base_dir):
file_path = os.path.join(base_dir, chat_id, "expressions.json")
if not os.path.exists(file_path):
continue
try:
with open(file_path, "r", encoding="utf-8") as f:
expressions = json.load(f)
# 应用全局衰减
decayed_expressions = self.apply_decay_to_expressions(expressions, current_time)
# 保存衰减后的结果
with open(file_path, "w", encoding="utf-8") as f:
json.dump(decayed_expressions, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"全局衰减{type}表达方式失败: {e}")
continue
# 学习新的表达方式(这里会进行局部衰减)
for _ in range(3):
learnt_style: Optional[List[Tuple[str, str, str]]] = await self.learn_and_store(type="style", num=25)
if not learnt_style:
return []
for _ in range(1):
learnt_grammar: Optional[List[Tuple[str, str, str]]] = await self.learn_and_store(type="grammar", num=10)
if not learnt_grammar:
return []
return learnt_style, learnt_grammar
def calculate_decay_factor(self, time_diff_days: float) -> float:
"""
计算衰减值
当时间差为0天时衰减值为0.001
当时间差为7天时衰减值为0
当时间差为30天时衰减值为0.001
使用二次函数进行曲线插值
"""
if time_diff_days <= 0 or time_diff_days >= DECAY_DAYS:
return 0.001
# 使用二次函数进行插值
# 将7天作为顶点0天和30天作为两个端点
# 使用顶点式y = a(x-h)^2 + k其中(h,k)为顶点
h = 7.0 # 顶点x坐标
k = 0.001 # 顶点y坐标
# 计算a值使得x=0和x=30时y=0.001
# 0.001 = a(0-7)^2 + 0.001
# 解得a = 0
a = 0
# 计算衰减值
decay = a * (time_diff_days - h) ** 2 + k
return min(0.001, decay)
def apply_decay_to_expressions(
self, expressions: List[Dict[str, Any]], current_time: float
) -> List[Dict[str, Any]]:
"""
对表达式列表应用衰减
返回衰减后的表达式列表移除count小于0的项
"""
result = []
for expr in expressions:
# 确保last_active_time存在如果不存在则使用current_time
if "last_active_time" not in expr:
expr["last_active_time"] = current_time
last_active = expr["last_active_time"]
time_diff_days = (current_time - last_active) / (24 * 3600) # 转换为天
decay_value = self.calculate_decay_factor(time_diff_days)
expr["count"] = max(0.01, expr.get("count", 1) - decay_value)
if expr["count"] > 0:
result.append(expr)
return result
async def learn_and_store(self, type: str, num: int = 10) -> List[Tuple[str, str, str]]:
"""
选择从当前到最近1小时内的随机num条消息然后学习这些消息的表达方式
type: "style" or "grammar"
"""
if type == "style":
type_str = "语言风格"
elif type == "grammar":
type_str = "句法特点"
else:
raise ValueError(f"Invalid type: {type}")
res = await self.learn_expression(type, num)
if res is None:
return []
learnt_expressions, chat_id = res
chat_stream = get_chat_manager().get_stream(chat_id)
if chat_stream.group_info:
group_name = chat_stream.group_info.group_name
else:
group_name = f"{chat_stream.user_info.user_nickname}的私聊"
learnt_expressions_str = ""
for _chat_id, situation, style in learnt_expressions:
learnt_expressions_str += f"{situation}->{style}\n"
logger.info(f"{group_name} 学习到{type_str}:\n{learnt_expressions_str}")
if not learnt_expressions:
logger.info(f"没有学习到{type_str}")
return []
# 按chat_id分组
chat_dict: Dict[str, List[Dict[str, str]]] = {}
for chat_id, situation, style in learnt_expressions:
if chat_id not in chat_dict:
chat_dict[chat_id] = []
chat_dict[chat_id].append({"situation": situation, "style": style})
current_time = time.time()
# 存储到/data/expression/对应chat_id/expressions.json
for chat_id, expr_list in chat_dict.items():
dir_path = os.path.join("data", "expression", f"learnt_{type}", str(chat_id))
os.makedirs(dir_path, exist_ok=True)
file_path = os.path.join(dir_path, "expressions.json")
# 若已存在,先读出合并
old_data: List[Dict[str, Any]] = []
if os.path.exists(file_path):
try:
with open(file_path, "r", encoding="utf-8") as f:
old_data = json.load(f)
except Exception:
old_data = []
# 应用衰减
# old_data = self.apply_decay_to_expressions(old_data, current_time)
# 合并逻辑
for new_expr in expr_list:
found = False
for old_expr in old_data:
if self.is_similar(new_expr["situation"], old_expr.get("situation", "")) and self.is_similar(
new_expr["style"], old_expr.get("style", "")
):
found = True
# 50%概率替换
if random.random() < 0.5:
old_expr["situation"] = new_expr["situation"]
old_expr["style"] = new_expr["style"]
old_expr["count"] = old_expr.get("count", 1) + 1
old_expr["last_active_time"] = current_time
break
if not found:
new_expr["count"] = 1
new_expr["last_active_time"] = current_time
old_data.append(new_expr)
# 处理超限问题
if len(old_data) > MAX_EXPRESSION_COUNT:
# 计算每个表达方式的权重count的倒数这样count越小的越容易被选中
weights = [1 / (expr.get("count", 1) + 0.1) for expr in old_data]
# 随机选择要移除的表达方式,避免重复索引
remove_count = len(old_data) - MAX_EXPRESSION_COUNT
# 使用一种不会选到重复索引的方法
indices = list(range(len(old_data)))
# 方法1使用numpy.random.choice
# 把列表转成一个映射字典,保证不会有重复
remove_set = set()
total_attempts = 0
# 尝试按权重随机选择,直到选够数量
while len(remove_set) < remove_count and total_attempts < len(old_data) * 2:
idx = random.choices(indices, weights=weights, k=1)[0]
remove_set.add(idx)
total_attempts += 1
# 如果没选够,随机补充
if len(remove_set) < remove_count:
remaining = set(indices) - remove_set
remove_set.update(random.sample(list(remaining), remove_count - len(remove_set)))
remove_indices = list(remove_set)
# 从后往前删除,避免索引变化
for idx in sorted(remove_indices, reverse=True):
old_data.pop(idx)
with open(file_path, "w", encoding="utf-8") as f:
json.dump(old_data, f, ensure_ascii=False, indent=2)
return learnt_expressions
async def learn_expression(self, type: str, num: int = 10) -> Optional[Tuple[List[Tuple[str, str, str]], str]]:
"""选择从当前到最近1小时内的随机num条消息然后学习这些消息的表达方式
Args:
type: "style" or "grammar"
"""
if type == "style":
type_str = "语言风格"
prompt = "learn_style_prompt"
elif type == "grammar":
type_str = "句法特点"
prompt = "learn_grammar_prompt"
else:
raise ValueError(f"Invalid type: {type}")
current_time = time.time()
random_msg: Optional[List[Dict[str, Any]]] = get_raw_msg_by_timestamp_random(
current_time - 3600 * 24, current_time, limit=num
)
# print(random_msg)
if not random_msg or random_msg == []:
return None
# 转化成str
chat_id: str = random_msg[0]["chat_id"]
# random_msg_str: str = build_readable_messages(random_msg, timestamp_mode="normal")
random_msg_str: str = await build_anonymous_messages(random_msg)
# print(f"random_msg_str:{random_msg_str}")
prompt: str = await global_prompt_manager.format_prompt(
prompt,
chat_str=random_msg_str,
)
logger.debug(f"学习{type_str}的prompt: {prompt}")
try:
response, _ = await self.express_learn_model.generate_response_async(prompt)
except Exception as e:
logger.error(f"学习{type_str}失败: {e}")
return None
logger.debug(f"学习{type_str}的response: {response}")
expressions: List[Tuple[str, str, str]] = self.parse_expression_response(response, chat_id)
return expressions, chat_id
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()
expression_learner = None
def get_expression_learner():
global expression_learner
if expression_learner is None:
expression_learner = ExpressionLearner()
return expression_learner

View File

@@ -21,8 +21,6 @@ from src.chat.heart_flow.observation.chatting_observation import ChattingObserva
from src.chat.heart_flow.observation.structure_observation import StructureObservation
from src.chat.heart_flow.observation.actions_observation import ActionObservation
from src.chat.focus_chat.info_processors.tool_processor import ToolProcessor
from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
from src.chat.focus_chat.memory_activator import MemoryActivator
from src.chat.focus_chat.info_processors.base_processor import BaseProcessor
from src.chat.focus_chat.info_processors.self_processor import SelfProcessor
@@ -125,9 +123,6 @@ class HeartFChatting:
self.processors: List[BaseProcessor] = []
self._register_default_processors()
self.expressor = DefaultExpressor(chat_stream=self.chat_stream)
self.replyer = DefaultReplyer(chat_stream=self.chat_stream)
self.action_manager = ActionManager()
self.action_planner = PlannerFactory.create_planner(
log_prefix=self.log_prefix, action_manager=self.action_manager
@@ -543,6 +538,7 @@ class HeartFChatting:
async def _observe_process_plan_action_loop(self, cycle_timers: dict, thinking_id: str) -> dict:
try:
loop_start_time = time.time()
with Timer("观察", cycle_timers):
# 执行所有观察器的观察
for observation in self.observations:
@@ -583,7 +579,7 @@ class HeartFChatting:
}
with Timer("规划器", cycle_timers):
plan_result = await self.action_planner.plan(all_plan_info, running_memorys)
plan_result = await self.action_planner.plan(all_plan_info, running_memorys, loop_start_time)
loop_plan_info = {
"action_result": plan_result.get("action_result", {}),
@@ -607,7 +603,7 @@ class HeartFChatting:
logger.debug(f"{self.log_prefix} 麦麦想要:'{action_str}'")
success, reply_text, command = await self._handle_action(
action_type, reasoning, action_data, cycle_timers, thinking_id, self.observations
action_type, reasoning, action_data, cycle_timers, thinking_id
)
loop_action_info = {
@@ -646,7 +642,6 @@ class HeartFChatting:
action_data: dict,
cycle_timers: dict,
thinking_id: str,
observations: List[Observation],
) -> tuple[bool, str, str]:
"""
处理规划动作,使用动作工厂创建相应的动作处理器
@@ -670,9 +665,6 @@ class HeartFChatting:
reasoning=reasoning,
cycle_timers=cycle_timers,
thinking_id=thinking_id,
observations=observations,
expressor=self.expressor,
replyer=self.replyer,
chat_stream=self.chat_stream,
log_prefix=self.log_prefix,
shutting_down=self._shutting_down,

View File

@@ -15,7 +15,7 @@ install(extra_lines=3)
logger = get_logger("sender")
async def send_message(message: MessageSending) -> str:
async def send_message(message: MessageSending) -> bool:
"""合并后的消息发送函数包含WS发送和日志记录"""
message_preview = truncate_message(message.processed_plain_text, max_length=40)
@@ -23,7 +23,7 @@ async def send_message(message: MessageSending) -> str:
# 直接调用API发送消息
await get_global_api().send_message(message)
logger.info(f"已将消息 '{message_preview}' 发往平台'{message.message_info.platform}'")
return message.processed_plain_text
return True
except Exception as e:
logger.error(f"发送消息 '{message_preview}' 发往平台'{message.message_info.platform}' 失败: {str(e)}")
@@ -73,17 +73,15 @@ class HeartFCSender:
thinking_message = self.thinking_messages.get(chat_id, {}).get(message_id)
return thinking_message.thinking_start_time if thinking_message else None
async def send_message(self, message: MessageSending, has_thinking=False, typing=False, set_reply=False):
async def send_message(self, message: MessageSending, typing=False, set_reply=False, storage_message=True):
"""
处理、发送并存储一条消息。
参数:
message: MessageSending 对象,待发送的消息。
has_thinking: 是否管理思考状态,表情包无思考状态(如需调用 register_thinking/complete_thinking
typing: 是否模拟打字等待(根据 has_thinking 控制等待时长)。
typing: 是否模拟打字等待
用法:
- has_thinking=True 时,自动处理思考消息的时间和清理。
- typing=True 时,发送前会有打字等待。
"""
if not message.chat_stream:
@@ -98,40 +96,29 @@ class HeartFCSender:
try:
if set_reply:
_ = message.update_thinking_time()
message.build_reply()
logger.debug(f"[{chat_id}] 选择回复引用消息: {message.processed_plain_text[:20]}...")
# --- 条件应用 set_reply 逻辑 ---
if (
message.is_head
and not message.is_private_message()
and message.reply.processed_plain_text != "[System Trigger Context]"
):
# message.set_reply(message.reply)
message.set_reply()
logger.debug(f"[{chat_id}] 应用 set_reply 逻辑: {message.processed_plain_text[:20]}...")
# print(f"message.display_message: {message.display_message}")
await message.process()
# print(f"message.display_message: {message.display_message}")
if typing:
if has_thinking:
typing_time = calculate_typing_time(
input_string=message.processed_plain_text,
thinking_start_time=message.thinking_start_time,
is_emoji=message.is_emoji,
)
await asyncio.sleep(typing_time)
else:
await asyncio.sleep(0.5)
typing_time = calculate_typing_time(
input_string=message.processed_plain_text,
thinking_start_time=message.thinking_start_time,
is_emoji=message.is_emoji,
)
await asyncio.sleep(typing_time)
sent_msg = await send_message(message)
await self.storage.store_message(message, message.chat_stream)
if not sent_msg:
return False
if sent_msg:
return sent_msg
else:
return "发送失败"
if storage_message:
await self.storage.store_message(message, message.chat_stream)
return sent_msg
except Exception as e:
logger.error(f"[{chat_id}] 处理或存储消息 {message_id} 时出错: {e}")

View File

@@ -172,6 +172,7 @@ class HeartFCMessageReceiver:
return
# 5. 消息存储
print(f"message: {message.message_info.time}")
await self.storage.store_message(message, chat)
# 6. 兴趣度计算与更新

View File

@@ -11,7 +11,7 @@ from src.chat.message_receive.chat_stream import get_chat_manager
from .base_processor import BaseProcessor
from src.chat.focus_chat.info.info_base import InfoBase
from src.chat.focus_chat.info.expression_selection_info import ExpressionSelectionInfo
from src.chat.focus_chat.expressors.exprssion_learner import get_expression_learner
from src.chat.express.exprssion_learner import get_expression_learner
from json_repair import repair_json
import json

View File

@@ -1,13 +1,9 @@
from typing import Dict, List, Optional, Type, Any
from src.plugin_system.base.base_action import BaseAction
from src.chat.heart_flow.observation.observation import Observation
from src.chat.focus_chat.replyer.default_replyer import DefaultReplyer
from src.chat.focus_chat.expressors.default_expressor import DefaultExpressor
from src.chat.message_receive.chat_stream import ChatStream
from src.common.logger import get_logger
# 不再需要导入动作类因为已经在main.py中导入
# import src.chat.actions.default_actions # noqa
from src.plugin_system.core.component_registry import component_registry
from src.plugin_system.base.component_types import ComponentType
logger = get_logger("action_manager")
@@ -15,87 +11,11 @@ logger = get_logger("action_manager")
ActionInfo = Dict[str, Any]
class PluginActionWrapper(BaseAction):
"""
新插件系统Action组件的兼容性包装器
将新插件系统的Action组件包装为旧系统兼容的BaseAction接口
"""
def __init__(
self, plugin_action, action_name: str, action_data: dict, reasoning: str, cycle_timers: dict, thinking_id: str
):
"""初始化包装器"""
# 调用旧系统BaseAction初始化只传递它能接受的参数
super().__init__(
action_data=action_data, reasoning=reasoning, cycle_timers=cycle_timers, thinking_id=thinking_id
)
# 存储插件Action实例它已经包含了所有必要的服务对象
self.plugin_action = plugin_action
self.action_name = action_name
# 从插件Action实例复制属性到包装器
self._sync_attributes_from_plugin_action()
def _sync_attributes_from_plugin_action(self):
"""从插件Action实例同步属性到包装器"""
# 基本属性
self.action_name = getattr(self.plugin_action, "action_name", self.action_name)
# 设置兼容的默认值
self.action_description = f"插件Action: {self.action_name}"
self.action_parameters = {}
self.action_require = []
# 激活类型属性(从新插件系统转换)
plugin_focus_type = getattr(self.plugin_action, "focus_activation_type", None)
plugin_normal_type = getattr(self.plugin_action, "normal_activation_type", None)
if plugin_focus_type:
self.focus_activation_type = (
plugin_focus_type.value if hasattr(plugin_focus_type, "value") else str(plugin_focus_type)
)
if plugin_normal_type:
self.normal_activation_type = (
plugin_normal_type.value if hasattr(plugin_normal_type, "value") else str(plugin_normal_type)
)
# 其他属性
self.random_activation_probability = getattr(self.plugin_action, "random_activation_probability", 0.0)
self.llm_judge_prompt = getattr(self.plugin_action, "llm_judge_prompt", "")
self.activation_keywords = getattr(self.plugin_action, "activation_keywords", [])
self.keyword_case_sensitive = getattr(self.plugin_action, "keyword_case_sensitive", False)
# 模式和并行设置
plugin_mode = getattr(self.plugin_action, "mode_enable", None)
if plugin_mode:
self.mode_enable = plugin_mode.value if hasattr(plugin_mode, "value") else str(plugin_mode)
self.parallel_action = getattr(self.plugin_action, "parallel_action", True)
self.enable_plugin = True
async def execute(self) -> tuple[bool, str]:
"""实现抽象方法execute委托给插件Action的execute方法"""
try:
# 调用插件Action的execute方法
success, response = await self.plugin_action.execute()
logger.debug(f"插件Action {self.action_name} 执行{'成功' if success else '失败'}: {response}")
return success, response
except Exception as e:
logger.error(f"插件Action {self.action_name} 执行异常: {e}")
return False, f"插件Action执行失败: {str(e)}"
async def handle_action(self) -> tuple[bool, str]:
"""兼容旧系统的动作处理接口委托给execute方法"""
return await self.execute()
class ActionManager:
"""
动作管理器,用于管理各种类型的动作
现在统一使用新插件系统,简化了原有的新旧兼容逻辑。
"""
# 类常量
@@ -119,23 +39,20 @@ class ActionManager:
# 初始化时将默认动作加载到使用中的动作
self._using_actions = self._default_actions.copy()
# 添加系统核心动作
# self._add_system_core_actions()
def _load_plugin_actions(self) -> None:
"""
加载所有插件目录中的动作
加载所有插件系统中的动作
"""
try:
# 从新插件系统获取Action组件
self._load_plugin_system_actions()
logger.debug("插件系统加载Action组件成功")
logger.debug("从插件系统加载Action组件成功")
except Exception as e:
logger.error(f"加载插件动作失败: {e}")
def _load_plugin_system_actions(self) -> None:
"""插件系统的component_registry加载Action组件"""
"""从插件系统的component_registry加载Action组件"""
try:
from src.plugin_system.core.component_registry import component_registry
from src.plugin_system.base.component_types import ComponentType
@@ -148,7 +65,7 @@ class ActionManager:
logger.debug(f"Action组件 {action_name} 已存在,跳过")
continue
# 将插件系统的ActionInfo转换为旧系统格式
# 将插件系统的ActionInfo转换为ActionManager格式
converted_action_info = {
"description": action_info.description,
"parameters": getattr(action_info, "action_parameters", {}),
@@ -165,8 +82,7 @@ class ActionManager:
# 模式和并行设置
"mode_enable": action_info.mode_enable.value,
"parallel_action": action_info.parallel_action,
# 标记这是来自新插件系统的组件
"_plugin_system_component": True,
# 插件信息
"_plugin_name": getattr(action_info, "plugin_name", ""),
}
@@ -180,7 +96,7 @@ class ActionManager:
f"从插件系统加载Action组件: {action_name} (插件: {getattr(action_info, 'plugin_name', 'unknown')})"
)
logger.info(f"插件系统加载了 {len(action_components)} 个Action组件")
logger.info(f"从插件系统加载了 {len(action_components)} 个Action组件")
except Exception as e:
logger.error(f"从插件系统加载Action组件失败: {e}")
@@ -195,12 +111,9 @@ class ActionManager:
reasoning: str,
cycle_timers: dict,
thinking_id: str,
observations: List[Observation],
chat_stream: ChatStream,
log_prefix: str,
shutting_down: bool = False,
expressor: DefaultExpressor = None,
replyer: DefaultReplyer = None,
) -> Optional[BaseAction]:
"""
创建动作处理器实例
@@ -211,9 +124,6 @@ class ActionManager:
reasoning: 执行理由
cycle_timers: 计时器字典
thinking_id: 思考ID
observations: 观察列表
expressor: 表达器
replyer: 回复器
chat_stream: 聊天流
log_prefix: 日志前缀
shutting_down: 是否正在关闭
@@ -221,122 +131,39 @@ class ActionManager:
Returns:
Optional[BaseAction]: 创建的动作处理器实例如果动作名称未注册则返回None
"""
# 检查动作是否在当前使用的动作集中
# if action_name not in self._using_actions:
# logger.warning(f"当前不可用的动作类型: {action_name}")
# return None
# 检查是否是新插件系统的Action组件
action_info = self._registered_actions.get(action_name)
if action_info and action_info.get("_plugin_system_component", False):
return self._create_plugin_system_action(
action_name,
action_data,
reasoning,
cycle_timers,
thinking_id,
observations,
chat_stream,
log_prefix,
shutting_down,
expressor,
replyer,
)
# 旧系统的动作创建逻辑
from src.plugin_system.core.component_registry import component_registry
action_registry = component_registry.get_action_registry()
handler_class = action_registry.get(action_name)
if not handler_class:
logger.warning(f"未注册的动作类型: {action_name}")
return None
try:
# 创建动作实例
instance = handler_class(
action_data=action_data,
reasoning=reasoning,
cycle_timers=cycle_timers,
thinking_id=thinking_id,
observations=observations,
expressor=expressor,
replyer=replyer,
chat_stream=chat_stream,
log_prefix=log_prefix,
shutting_down=shutting_down,
)
return instance
except Exception as e:
logger.error(f"创建动作处理器实例失败: {e}")
return None
def _create_plugin_system_action(
self,
action_name: str,
action_data: dict,
reasoning: str,
cycle_timers: dict,
thinking_id: str,
observations: List[Observation],
chat_stream: ChatStream,
log_prefix: str,
shutting_down: bool = False,
expressor: DefaultExpressor = None,
replyer: DefaultReplyer = None,
) -> Optional["PluginActionWrapper"]:
"""
创建新插件系统的Action组件实例并包装为兼容旧系统的接口
Returns:
Optional[PluginActionWrapper]: 包装后的Action实例
"""
try:
from src.plugin_system.core.component_registry import component_registry
# 获取组件类
component_class = component_registry.get_component_class(action_name)
# 获取组件类 - 明确指定查询Action类型
component_class = component_registry.get_component_class(action_name, ComponentType.ACTION)
if not component_class:
logger.error(f"未找到插件Action组件: {action_name}")
logger.warning(f"{log_prefix} 未找到Action组件: {action_name}")
return None
# 获取组件信息
component_info = component_registry.get_component_info(action_name, ComponentType.ACTION)
if not component_info:
logger.warning(f"{log_prefix} 未找到Action组件信息: {action_name}")
return None
# 获取插件配置
component_info = component_registry.get_component_info(action_name)
plugin_config = None
if component_info and component_info.plugin_name:
plugin_config = component_registry.get_plugin_config(component_info.plugin_name)
plugin_config = component_registry.get_plugin_config(component_info.plugin_name)
# 创建插件Action实例
plugin_action_instance = component_class(
# 创建动作实例
instance = component_class(
action_data=action_data,
reasoning=reasoning,
cycle_timers=cycle_timers,
thinking_id=thinking_id,
chat_stream=chat_stream,
expressor=expressor,
replyer=replyer,
observations=observations,
log_prefix=log_prefix,
shutting_down=shutting_down,
plugin_config=plugin_config,
)
# 创建兼容性包装器
wrapper = PluginActionWrapper(
plugin_action=plugin_action_instance,
action_name=action_name,
action_data=action_data,
reasoning=reasoning,
cycle_timers=cycle_timers,
thinking_id=thinking_id,
)
logger.debug(f"创建插件Action实例成功: {action_name}")
return wrapper
logger.debug(f"创建Action实例成功: {action_name}")
return instance
except Exception as e:
logger.error(f"创建插件Action实例失败 {action_name}: {e}")
logger.error(f"创建Action实例失败 {action_name}: {e}")
import traceback
logger.error(traceback.format_exc())
@@ -366,19 +193,13 @@ class ActionManager:
"""
filtered_actions = {}
# print(self._using_actions)
for action_name, action_info in self._using_actions.items():
# print(f"action_info: {action_info}")
# print(f"action_name: {action_name}")
action_mode = action_info.get("mode_enable", "all")
# 检查动作是否在当前模式下启用
if action_mode == "all" or action_mode == mode:
filtered_actions[action_name] = action_info
logger.debug(f"动作 {action_name} 在模式 {mode} 下可用 (mode_enable: {action_mode})")
# else:
# logger.debug(f"动作 {action_name} 在模式 {mode} 下不可用 (mode_enable: {action_mode})")
logger.debug(f"模式 {mode} 下可用动作: {list(filtered_actions.keys())}")
return filtered_actions
@@ -474,20 +295,6 @@ class ActionManager:
def restore_default_actions(self) -> None:
"""恢复默认动作集到使用集"""
self._using_actions = self._default_actions.copy()
# 添加系统核心动作即使enable_plugin为False的系统动作
# self._add_system_core_actions()
# def _add_system_core_actions(self) -> None:
# """
# 添加系统核心动作到使用集
# 系统核心动作是那些enable_plugin为False但是系统必需的动作
# """
# system_core_actions = ["exit_focus_chat"] # 可以根据需要扩展
# for action_name in system_core_actions:
# if action_name in self._registered_actions and action_name not in self._using_actions:
# self._using_actions[action_name] = self._registered_actions[action_name]
# logger.debug(f"添加系统核心动作到使用集: {action_name}")
def add_system_action_if_needed(self, action_name: str) -> bool:
"""
@@ -517,5 +324,4 @@ class ActionManager:
"""
from src.plugin_system.core.component_registry import component_registry
action_registry = component_registry.get_action_registry()
return action_registry.get(action_name)
return component_registry.get_component_class(action_name)

View File

@@ -12,14 +12,14 @@ class BasePlanner(ABC):
self.action_manager = action_manager
@abstractmethod
async def plan(self, all_plan_info: List[InfoBase], running_memorys: List[Dict[str, Any]]) -> Dict[str, Any]:
async def plan(self, all_plan_info: List[InfoBase], running_memorys: List[Dict[str, Any]], loop_start_time: float) -> Dict[str, Any]:
"""
规划下一步行动
Args:
all_plan_info: 所有计划信息
running_memorys: 回忆信息
loop_start_time: 循环开始时间
Returns:
Dict[str, Any]: 规划结果
"""

View File

@@ -242,6 +242,8 @@ class ActionModifier:
for action_name, action_info in actions_with_info.items():
activation_type = action_info.get("focus_activation_type", "always")
print(f"action_name: {action_name}, activation_type: {activation_type}")
# 现在统一是字符串格式的激活类型值
if activation_type == "always":

View File

@@ -32,11 +32,7 @@ def init_prompt():
{self_info_block}
请记住你的性格,身份和特点。
{extra_info_block}
{memory_str}
{time_block}
你是群内的一员,你现在正在参与群内的闲聊,以下是群内的聊天内容:
{chat_content_block}
@@ -86,13 +82,14 @@ class ActionPlanner(BasePlanner):
request_type="focus.planner", # 用于动作规划
)
async def plan(self, all_plan_info: List[InfoBase], running_memorys: List[Dict[str, Any]]) -> Dict[str, Any]:
async def plan(self, all_plan_info: List[InfoBase], running_memorys: List[Dict[str, Any]], loop_start_time: float) -> Dict[str, Any]:
"""
规划器 (Planner): 使用LLM根据上下文决定做出什么动作。
参数:
all_plan_info: 所有计划信息
running_memorys: 回忆信息
loop_start_time: 循环开始时间
"""
action = "no_reply" # 默认动作
@@ -246,6 +243,8 @@ class ActionPlanner(BasePlanner):
if selected_expressions:
action_data["selected_expressions"] = selected_expressions
logger.debug(f"{self.log_prefix} 传递{len(selected_expressions)}个选中的表达方式到action_data")
action_data["loop_start_time"] = loop_start_time
# 对于reply动作不需要额外处理因为相关字段已经在上面的循环中添加到action_data
@@ -326,7 +325,7 @@ class ActionPlanner(BasePlanner):
chat_content_block = ""
if observed_messages_str:
chat_content_block = f"聊天记录:\n{observed_messages_str}"
chat_content_block = f"\n{observed_messages_str}"
else:
chat_content_block = "你还未开始聊天"
@@ -387,7 +386,7 @@ class ActionPlanner(BasePlanner):
prompt = planner_prompt_template.format(
relation_info_block=relation_info_block,
self_info_block=self_info_block,
memory_str=memory_str,
# memory_str=memory_str,
time_block=time_block,
# bot_name=global_config.bot.nickname,
prompt_personality=personality_block,
@@ -397,7 +396,7 @@ class ActionPlanner(BasePlanner):
cycle_info_block=cycle_info,
action_options_text=action_options_block,
# action_available_block=action_available_block,
extra_info_block=extra_info_block,
# extra_info_block=extra_info_block,
moderation_prompt=moderation_prompt_block,
)
return prompt

View File

@@ -8,9 +8,7 @@ from src.chat.message_receive.chat_stream import get_chat_manager
from src.common.logger import get_logger
from src.llm_models.utils_model import LLMRequest
from src.config.config import global_config
from src.chat.utils.utils_image import image_path_to_base64 # Local import needed after move
from src.chat.utils.timer_calculator import Timer # <--- Import Timer
from src.chat.emoji_system.emoji_manager import get_emoji_manager
from src.chat.focus_chat.heartFC_sender import HeartFCSender
from src.chat.utils.utils import process_llm_response
from src.chat.heart_flow.utils_chat import get_chat_type_and_target_info
@@ -18,6 +16,7 @@ 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.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.express.exprssion_learner import get_expression_learner
import time
import random
from datetime import datetime
@@ -50,7 +49,7 @@ def init_prompt():
不要浮夸不要夸张修辞只输出一条回复就好
现在你说
""",
"default_replyer_prompt",
"default_generator_prompt",
)
Prompt(
@@ -70,7 +69,51 @@ def init_prompt():
不要浮夸不要夸张修辞只输出一条回复就好
现在你说
""",
"default_replyer_private_prompt",
"default_generator_private_prompt",
)
Prompt(
"""
你可以参考你的以下的语言习惯如果情景合适就使用不要盲目使用,不要生硬使用而是结合到表达中
{style_habbits}
你现在正在群里聊天以下是群里正在进行的聊天内容
{chat_info}
以上是聊天内容你需要了解聊天记录中的内容
{chat_target}
你的名字是{bot_name}{prompt_personality}在这聊天中"{sender_name}"说的"{target_message}"引起了你的注意对这句话你想表达{raw_reply},原因是{reason}你现在要思考怎么回复
你需要使用合适的语法和句法参考聊天内容组织一条日常且口语化的回复请你修改你想表达的原句符合你的表达风格和语言习惯
请你根据情景使用以下句法
{grammar_habbits}
{config_expression_style}你可以完全重组回复保留最基本的表达含义就好但重组后保持语意通顺
不要浮夸不要夸张修辞平淡且不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )只输出一条回复就好
现在你说
""",
"default_expressor_prompt",
)
Prompt(
"""
你可以参考以下的语言习惯如果情景合适就使用不要盲目使用,不要生硬使用而是结合到表达中
{style_habbits}
你现在正在群里聊天以下是群里正在进行的聊天内容
{chat_info}
以上是聊天内容你需要了解聊天记录中的内容
{chat_target}
你的名字是{bot_name}{prompt_personality}在这聊天中"{sender_name}"说的"{target_message}"引起了你的注意对这句话你想表达{raw_reply},原因是{reason}你现在要思考怎么回复
你需要使用合适的语法和句法参考聊天内容组织一条日常且口语化的回复
请你根据情景使用以下句法
{grammar_habbits}
{config_expression_style}你可以完全重组回复保留最基本的表达含义就好但重组后保持语意通顺
不要浮夸不要夸张修辞平淡且不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )只输出一条回复就好
现在你说
""",
"default_expressor_private_prompt", # New template for private FOCUSED chat
)
@@ -84,9 +127,8 @@ class DefaultReplyer:
)
self.heart_fc_sender = HeartFCSender()
self.chat_id = chat_stream.stream_id
self.chat_stream = chat_stream
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_id)
self.is_group_chat, self.chat_target_info = get_chat_type_and_target_info(self.chat_stream.stream_id)
async def _create_thinking_message(self, anchor_message: Optional[MessageRecv], thinking_id: str):
"""创建思考消息 (尝试锚定到 anchor_message)"""
@@ -114,213 +156,152 @@ class DefaultReplyer:
await self.heart_fc_sender.register_thinking(thinking_message)
return None
async def deal_reply(
async def generate_reply_with_context(
self,
cycle_timers: dict,
action_data: Dict[str, Any],
reasoning: str,
anchor_message: MessageRecv,
thinking_id: str,
) -> tuple[bool, Optional[List[Tuple[str, str]]]]:
# 创建思考消息
await self._create_thinking_message(anchor_message, thinking_id)
reply = [] # 初始化 reply防止未定义
try:
has_sent_something = False
# 处理文本部分
# text_part = action_data.get("text", [])
# if text_part:
sent_msg_list = []
with Timer("生成回复", cycle_timers):
# 可以保留原有的文本处理逻辑或进行适当调整
reply = await self.reply(
# in_mind_reply=text_part,
anchor_message=anchor_message,
thinking_id=thinking_id,
reason=reasoning,
action_data=action_data,
)
if reply:
with Timer("发送消息", cycle_timers):
sent_msg_list = await self.send_response_messages(
anchor_message=anchor_message,
thinking_id=thinking_id,
response_set=reply,
)
has_sent_something = True
else:
logger.warning(f"{self.log_prefix} 文本回复生成失败")
if not has_sent_something:
logger.warning(f"{self.log_prefix} 回复动作未包含任何有效内容")
return has_sent_something, sent_msg_list
except Exception as e:
logger.error(f"回复失败: {e}")
traceback.print_exc()
return False, None
# --- 回复器 (Replier) 的定义 --- #
async def deal_emoji(
self,
anchor_message: MessageRecv,
thinking_id: str,
action_data: Dict[str, Any],
cycle_timers: dict,
) -> Optional[List[str]]:
"""
表情动作处理类
"""
await self._create_thinking_message(anchor_message, thinking_id)
try:
has_sent_something = False
sent_msg_list = []
reply = []
with Timer("选择表情", cycle_timers):
emoji_keyword = action_data.get("description", [])
emoji_base64, _description, emotion = await self._choose_emoji(emoji_keyword)
if emoji_base64:
# logger.info(f"选择表情: {_description}")
reply.append(("emoji", emoji_base64))
else:
logger.warning(f"{self.log_prefix} 没有找到合适表情")
if reply:
with Timer("发送表情", cycle_timers):
sent_msg_list = await self.send_response_messages(
anchor_message=anchor_message,
thinking_id=thinking_id,
response_set=reply,
)
has_sent_something = True
else:
logger.warning(f"{self.log_prefix} 表情发送失败")
if not has_sent_something:
logger.warning(f"{self.log_prefix} 表情发送失败")
return has_sent_something, sent_msg_list
except Exception as e:
logger.error(f"回复失败: {e}")
traceback.print_exc()
return False, None
async def reply(
self,
# in_mind_reply: str,
reason: str,
anchor_message: MessageRecv,
thinking_id: str,
action_data: Dict[str, Any],
) -> Optional[List[str]]:
reply_data: Dict[str, Any],
) -> Tuple[bool, Optional[List[str]]]:
"""
回复器 (Replier): 核心逻辑负责生成回复文本
(已整合原 HeartFCGenerator 的功能)
"""
try:
# 1. 获取情绪影响因子并调整模型温度
# arousal_multiplier = mood_manager.get_arousal_multiplier()
# current_temp = float(global_config.model.normal["temp"]) * arousal_multiplier
# self.express_model.params["temperature"] = current_temp # 动态调整温度
reply_to = action_data.get("reply_to", "none")
sender = ""
targer = ""
if ":" in reply_to or "" in reply_to:
# 使用正则表达式匹配中文或英文冒号
parts = re.split(pattern=r"[:]", string=reply_to, maxsplit=1)
if len(parts) == 2:
sender = parts[0].strip()
targer = parts[1].strip()
identity = action_data.get("identity", "")
extra_info_block = action_data.get("extra_info_block", "")
relation_info_block = action_data.get("relation_info_block", "")
# 3. 构建 Prompt
with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_focus(
chat_stream=self.chat_stream, # Pass the stream object
# in_mind_reply=in_mind_reply,
identity=identity,
extra_info_block=extra_info_block,
relation_info_block=relation_info_block,
reason=reason,
sender_name=sender, # Pass determined name
target_message=targer,
config_expression_style=global_config.expression.expression_style,
action_data=action_data, # 传递action_data
prompt = await self.build_prompt_reply_context(
reply_data=reply_data, # 传递action_data
)
# 4. 调用 LLM 生成回复
content = None
reasoning_content = None
model_name = "unknown_model"
if not prompt:
logger.error(f"{self.log_prefix}[Replier-{thinking_id}] Prompt 构建失败,无法生成回复。")
return None
try:
with Timer("LLM生成", {}): # 内部计时器,可选保留
logger.info(f"{self.log_prefix}Prompt:\n{prompt}\n")
content, (reasoning_content, model_name) = await self.express_model.generate_response_async(prompt)
# logger.info(f"prompt: {prompt}")
logger.info(f"最终回复: {content}")
except Exception as llm_e:
# 精简报错信息
logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}")
return None # LLM 调用失败则无法生成回复
return False, None # LLM 调用失败则无法生成回复
processed_response = process_llm_response(content)
# 5. 处理 LLM 响应
if not content:
logger.warning(f"{self.log_prefix}LLM 生成了空内容。")
return None
return False, None
if not processed_response:
logger.warning(f"{self.log_prefix}处理后的回复为空。")
return None
return False, None
reply_set = []
for str in processed_response:
reply_seg = ("text", str)
reply_set.append(reply_seg)
return reply_set
return True , reply_set
except Exception as e:
logger.error(f"{self.log_prefix}回复生成意外失败: {e}")
traceback.print_exc()
return None
async def build_prompt_focus(
return False, None
async def rewrite_reply_with_context(
self,
reason,
chat_stream,
sender_name,
# in_mind_reply,
extra_info_block,
relation_info_block,
identity,
target_message,
config_expression_style,
action_data=None,
# stuation,
reply_data: Dict[str, Any],
) -> Tuple[bool, Optional[List[str]]]:
"""
表达器 (Expressor): 核心逻辑负责生成回复文本
"""
try:
reply_to = reply_data.get("reply_to", "")
raw_reply = reply_data.get("raw_reply", "")
reason = reply_data.get("reason", "")
with Timer("构建Prompt", {}): # 内部计时器,可选保留
prompt = await self.build_prompt_rewrite_context(
raw_reply=raw_reply,
reason=reason,
reply_to=reply_to,
)
content = None
reasoning_content = None
model_name = "unknown_model"
if not prompt:
logger.error(f"{self.log_prefix}Prompt 构建失败,无法生成回复。")
return False, None
try:
with Timer("LLM生成", {}): # 内部计时器,可选保留
# TODO: API-Adapter修改标记
content, (reasoning_content, model_name) = await self.express_model.generate_response_async(prompt)
logger.info(f"想要表达:{raw_reply}||理由:{reason}")
logger.info(f"最终回复: {content}\n")
except Exception as llm_e:
# 精简报错信息
logger.error(f"{self.log_prefix}LLM 生成失败: {llm_e}")
return False, None # LLM 调用失败则无法生成回复
processed_response = process_llm_response(content)
# 5. 处理 LLM 响应
if not content:
logger.warning(f"{self.log_prefix}LLM 生成了空内容。")
return False, None
if not processed_response:
logger.warning(f"{self.log_prefix}处理后的回复为空。")
return False, None
reply_set = []
for str in processed_response:
reply_seg = ("text", str)
reply_set.append(reply_seg)
return True, reply_set
except Exception as e:
logger.error(f"{self.log_prefix}回复生成意外失败: {e}")
traceback.print_exc()
return False, None
async def build_prompt_reply_context(
self,
reply_data=None,
) -> str:
chat_stream = self.chat_stream
is_group_chat = bool(chat_stream.group_info)
identity = reply_data.get("identity", "")
extra_info_block = reply_data.get("extra_info_block", "")
relation_info_block = reply_data.get("relation_info_block", "")
reply_to = reply_data.get("reply_to", "none")
sender = ""
target = ""
if ":" in reply_to or "" in reply_to:
# 使用正则表达式匹配中文或英文冒号
parts = re.split(pattern=r"[:]", string=reply_to, maxsplit=1)
if len(parts) == 2:
sender = parts[0].strip()
target = parts[1].strip()
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id,
@@ -341,7 +322,7 @@ class DefaultReplyer:
grammar_habbits = []
# 使用从处理器传来的选中表达方式
selected_expressions = action_data.get("selected_expressions", []) if action_data else []
selected_expressions = reply_data.get("selected_expressions", []) if reply_data else []
if selected_expressions:
logger.info(f"{self.log_prefix} 使用处理器选中的{len(selected_expressions)}个表达方式")
@@ -371,7 +352,7 @@ class DefaultReplyer:
try:
# 处理关键词规则
for rule in global_config.keyword_reaction.keyword_rules:
if any(keyword in target_message for keyword in rule.keywords):
if any(keyword in target for keyword in rule.keywords):
logger.info(f"检测到关键词规则:{rule.keywords},触发反应:{rule.reaction}")
keywords_reaction_prompt += f"{rule.reaction}"
@@ -380,7 +361,7 @@ class DefaultReplyer:
for pattern_str in rule.regex:
try:
pattern = re.compile(pattern_str)
if result := pattern.search(target_message):
if result := pattern.search(target):
reaction = rule.reaction
for name, content in result.groupdict().items():
reaction = reaction.replace(f"[{name}]", content)
@@ -397,18 +378,18 @@ class DefaultReplyer:
# logger.debug("开始构建 focus prompt")
if sender_name:
if sender:
reply_target_block = (
f"现在{sender_name}说的:{target_message}。引起了你的注意,你想要在群里发言或者回复这条消息。"
f"现在{sender}说的:{target}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif target_message:
reply_target_block = f"现在{target_message}引起了你的注意,你想要在群里发言或者回复这条消息。"
elif target:
reply_target_block = f"现在{target}引起了你的注意,你想要在群里发言或者回复这条消息。"
else:
reply_target_block = "现在,你想要在群里发言或者回复消息。"
# --- Choose template based on chat type ---
if is_group_chat:
template_name = "default_replyer_prompt"
template_name = "default_generator_prompt"
# Group specific formatting variables (already fetched or default)
chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1")
# chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
@@ -422,18 +403,14 @@ class DefaultReplyer:
relation_info_block=relation_info_block,
time_block=time_block,
reply_target_block=reply_target_block,
# bot_name=global_config.bot.nickname,
# prompt_personality="",
# reason=reason,
# in_mind_reply=in_mind_reply,
keywords_reaction_prompt=keywords_reaction_prompt,
identity=identity,
target_message=target_message,
sender_name=sender_name,
config_expression_style=config_expression_style,
target_message=target,
sender_name=sender,
config_expression_style=global_config.expression.expression_style,
)
else: # Private chat
template_name = "default_replyer_private_prompt"
template_name = "default_generator_private_prompt"
chat_target_1 = "你正在和人私聊"
prompt = await global_prompt_manager.format_prompt(
template_name,
@@ -444,20 +421,125 @@ class DefaultReplyer:
relation_info_block=relation_info_block,
time_block=time_block,
reply_target_block=reply_target_block,
# bot_name=global_config.bot.nickname,
# prompt_personality="",
# reason=reason,
# in_mind_reply=in_mind_reply,
keywords_reaction_prompt=keywords_reaction_prompt,
identity=identity,
target_message=target_message,
sender_name=sender_name,
config_expression_style=config_expression_style,
target_message=target,
sender_name=sender,
config_expression_style=global_config.expression.expression_style,
)
return prompt
# --- 发送器 (Sender) --- #
async def build_prompt_rewrite_context(
self,
reason,
raw_reply,
reply_to,
) -> str:
sender = ""
target = ""
if ":" in reply_to or "" in reply_to:
# 使用正则表达式匹配中文或英文冒号
parts = re.split(pattern=r"[:]", string=reply_to, maxsplit=1)
if len(parts) == 2:
sender = parts[0].strip()
target = parts[1].strip()
chat_stream = self.chat_stream
is_group_chat = bool(chat_stream.group_info)
message_list_before_now = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_stream.stream_id,
timestamp=time.time(),
limit=global_config.focus_chat.observation_context_size,
)
chat_talking_prompt = build_readable_messages(
message_list_before_now,
replace_bot_name=True,
merge_messages=True,
timestamp_mode="relative",
read_mark=0.0,
truncate=True,
)
expression_learner = get_expression_learner()
(
learnt_style_expressions,
learnt_grammar_expressions,
personality_expressions,
) = await expression_learner.get_expression_by_chat_id(chat_stream.stream_id)
style_habbits = []
grammar_habbits = []
# 1. learnt_expressions加权随机选3条
if learnt_style_expressions:
weights = [expr["count"] for expr in learnt_style_expressions]
selected_learnt = weighted_sample_no_replacement(learnt_style_expressions, weights, 3)
for expr in selected_learnt:
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
style_habbits.append(f"{expr['situation']}时,使用 {expr['style']}")
# 2. learnt_grammar_expressions加权随机选3条
if learnt_grammar_expressions:
weights = [expr["count"] for expr in learnt_grammar_expressions]
selected_learnt = weighted_sample_no_replacement(learnt_grammar_expressions, weights, 3)
for expr in selected_learnt:
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
grammar_habbits.append(f"{expr['situation']}时,使用 {expr['style']}")
# 3. personality_expressions随机选1条
if personality_expressions:
expr = random.choice(personality_expressions)
if isinstance(expr, dict) and "situation" in expr and "style" in expr:
style_habbits.append(f"{expr['situation']}时,使用 {expr['style']}")
style_habbits_str = "\n".join(style_habbits)
grammar_habbits_str = "\n".join(grammar_habbits)
logger.debug("开始构建 focus prompt")
# --- Choose template based on chat type ---
if is_group_chat:
template_name = "default_expressor_prompt"
# Group specific formatting variables (already fetched or default)
chat_target_1 = await global_prompt_manager.get_prompt_async("chat_target_group1")
# chat_target_2 = await global_prompt_manager.get_prompt_async("chat_target_group2")
prompt = await global_prompt_manager.format_prompt(
template_name,
style_habbits=style_habbits_str,
grammar_habbits=grammar_habbits_str,
chat_target=chat_target_1,
chat_info=chat_talking_prompt,
bot_name=global_config.bot.nickname,
prompt_personality="",
reason=reason,
raw_reply=raw_reply,
sender_name=sender,
target_message=target,
config_expression_style=global_config.expression.expression_style,
)
else: # Private chat
template_name = "default_expressor_private_prompt"
chat_target_1 = "你正在和人私聊"
prompt = await global_prompt_manager.format_prompt(
template_name,
style_habbits=style_habbits_str,
grammar_habbits=grammar_habbits_str,
chat_target=chat_target_1,
chat_info=chat_talking_prompt,
bot_name=global_config.bot.nickname,
prompt_personality="",
reason=reason,
raw_reply=raw_reply,
sender_name=sender,
target_message=target,
config_expression_style=global_config.expression.expression_style,
)
return prompt
async def send_response_messages(
self,
@@ -468,7 +550,7 @@ class DefaultReplyer:
) -> Optional[MessageSending]:
"""发送回复消息 (尝试锚定到 anchor_message),使用 HeartFCSender"""
chat = self.chat_stream
chat_id = self.chat_id
chat_id = self.chat_stream.stream_id
if chat is None:
logger.error(f"{self.log_prefix} 无法发送回复chat_stream 为空。")
return None
@@ -514,7 +596,7 @@ class DefaultReplyer:
is_emoji = False
reply_to = not mark_head
bot_message = await self._build_single_sending_message(
bot_message: MessageSending = await self._build_single_sending_message(
anchor_message=anchor_message,
message_id=part_message_id,
message_segment=message_segment,
@@ -526,22 +608,22 @@ class DefaultReplyer:
)
try:
if (bot_message.is_private_message() or
bot_message.reply.processed_plain_text != "[System Trigger Context]" or
mark_head):
set_reply = False
else:
set_reply = True
if not mark_head:
mark_head = True
# first_bot_msg = bot_message # 保存第一个成功发送的消息对象
typing = False
else:
typing = True
if type == "emoji":
typing = False
if anchor_message.raw_message:
set_reply = True
else:
set_reply = False
sent_msg = await self.heart_fc_sender.send_message(
bot_message, has_thinking=True, typing=typing, set_reply=set_reply
bot_message, typing=typing, set_reply=set_reply
)
reply_message_ids.append(part_message_id) # 记录我们生成的ID
@@ -562,30 +644,15 @@ class DefaultReplyer:
return sent_msg_list
async def _choose_emoji(self, send_emoji: str):
"""
选择表情根据send_emoji文本选择表情返回表情base64
"""
emoji_base64 = ""
description = ""
emoji_raw = await get_emoji_manager().get_emoji_for_text(send_emoji)
if emoji_raw:
emoji_path, description, _emotion = emoji_raw
emoji_base64 = image_path_to_base64(emoji_path)
return emoji_base64, description, _emotion
else:
return None, None, None
async def _build_single_sending_message(
self,
anchor_message: MessageRecv,
message_id: str,
message_segment: Seg,
reply_to: bool,
is_emoji: bool,
thinking_id: str,
thinking_start_time: float,
display_message: str,
anchor_message: MessageRecv = None
) -> MessageSending:
"""构建单个发送消息"""
@@ -596,12 +663,16 @@ class DefaultReplyer:
)
# await anchor_message.process()
if anchor_message:
sender_info = anchor_message.message_info.user_info
else:
sender_info = None
bot_message = MessageSending(
message_id=message_id, # 使用片段的唯一ID
chat_stream=self.chat_stream,
bot_user_info=bot_user_info,
sender_info=anchor_message.message_info.user_info,
sender_info=sender_info,
message_segment=message_segment,
reply=anchor_message, # 回复原始锚点
is_head=reply_to,