fix:修复taget中的图片展示错误的问题

pull/1294/head
SengokuCola 2025-10-05 15:07:08 +08:00
parent 6a546a7c2b
commit a3009e0128
6 changed files with 253 additions and 114 deletions

View File

@ -1,4 +1,5 @@
import asyncio
from multiprocessing import context
import time
import traceback
import random
@ -196,19 +197,22 @@ class HeartFChatting:
# print(f"{self.log_prefix} questioned: {self.questioned},len: {len(global_conflict_tracker.get_questions_by_chat_id(self.stream_id))}")
if question_probability > 0 and not self.questioned and len(global_conflict_tracker.get_questions_by_chat_id(self.stream_id)) == 0: #长久没有回复,可以试试主动发言,提问概率随着时间增加
logger.info(f"{self.log_prefix} 长久没有回复,可以试试主动发言,概率: {question_probability}")
# logger.info(f"{self.log_prefix} 长久没有回复,可以试试主动发言,概率: {question_probability}")
if random.random() < question_probability: # 30%概率主动发言
try:
self.questioned = True
self.last_active_time = time.time()
print(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题")
# print(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题")
logger.info(f"{self.log_prefix} 长久没有回复,可以试试主动发言,开始生成问题")
cycle_timers, thinking_id = self.start_cycle()
question_maker = QuestionMaker(self.stream_id)
question, conflict_context = await question_maker.make_question()
logger.info(f"{self.log_prefix} 问题: {question}")
question, context,conflict_context = await question_maker.make_question()
if question:
logger.info(f"{self.log_prefix} 问题: {question}")
await global_conflict_tracker.track_conflict(question, conflict_context, True, self.stream_id)
await self._lift_question_reply(question,cycle_timers,thinking_id)
await self._lift_question_reply(question,context,cycle_timers,thinking_id)
else:
logger.info(f"{self.log_prefix} 无问题")
# self.end_cycle(cycle_timers, thinking_id)
except Exception as e:
logger.error(f"{self.log_prefix} 主动提问失败: {e}")
@ -548,8 +552,8 @@ class HeartFChatting:
traceback.print_exc()
return False, ""
async def _lift_question_reply(self, question: str, cycle_timers: Dict[str, float], thinking_id: str):
reason = f"你对问题\"{question}\"感到好奇,想要和群友讨论"
async def _lift_question_reply(self, question: str, context: str, cycle_timers: Dict[str, float], thinking_id: str):
reason = f"在聊天中:\n{context}\n你对问题\"{question}\"感到好奇,想要和群友讨论"
new_msg = get_raw_msg_before_timestamp_with_chat(
chat_id=self.stream_id,
timestamp=time.time(),
@ -564,19 +568,8 @@ class HeartFChatting:
available_actions=None,
loop_start_time=time.time(),
action_reasoning=reason)
self.action_planner.add_plan_log(reasoning=reason, actions=[reply_action_info])
self.action_planner.add_plan_log(reasoning=f"你对问题\"{question}\"感到好奇,想要和群友讨论", actions=[reply_action_info])
await database_api.store_action_info(
chat_stream=self.chat_stream,
action_build_into_prompt=False,
action_prompt_display=reason,
action_done=True,
thinking_id=thinking_id,
action_data=reply_action_info.action_data,
action_name="reply",
action_reasoning=reason,
)
success, llm_response = await generator_api.rewrite_reply(
chat_stream=self.chat_stream,
reply_data={

View File

@ -27,6 +27,7 @@ from src.chat.utils.chat_message_builder import (
replace_user_references,
)
from src.chat.express.expression_selector import expression_selector
from src.plugin_system.apis.message_api import translate_pid_to_description
# from src.memory_system.memory_activator import MemoryActivator
from src.person_info.person_info import Person
@ -358,6 +359,59 @@ class DefaultReplyer:
target = parts[1].strip()
return sender, target
def _replace_picids_with_descriptions(self, text: str) -> str:
"""将文本中的[picid:xxx]替换为具体的图片描述
Args:
text: 包含picid标记的文本
Returns:
替换后的文本
"""
# 匹配 [picid:xxxxx] 格式
pic_pattern = r"\[picid:([^\]]+)\]"
def replace_pic_id(match: re.Match) -> str:
pic_id = match.group(1)
description = translate_pid_to_description(pic_id)
return f"[图片:{description}]"
return re.sub(pic_pattern, replace_pic_id, text)
def _analyze_target_content(self, target: str) -> Tuple[bool, bool, str, str]:
"""分析target内容类型基于原始picid格式
Args:
target: 目标消息内容包含[picid:xxx]格式
Returns:
Tuple[bool, bool, str, str]: (是否只包含图片, 是否包含文字, 图片部分, 文字部分)
"""
if not target or not target.strip():
return False, False, "", ""
# 检查是否只包含picid标记
picid_pattern = r"\[picid:[^\]]+\]"
picid_matches = re.findall(picid_pattern, target)
# 移除所有picid标记后检查是否还有文字内容
text_without_picids = re.sub(picid_pattern, "", target).strip()
has_only_pics = len(picid_matches) > 0 and not text_without_picids
has_text = bool(text_without_picids)
# 提取图片部分(转换为[图片:描述]格式)
pic_part = ""
if picid_matches:
pic_descriptions = []
for picid_match in picid_matches:
pic_id = picid_match[6:-1] # 提取picid:xxx中的xxx部分
description = translate_pid_to_description(pic_id)
pic_descriptions.append(f"[图片:{description}]")
pic_part = "".join(pic_descriptions)
return has_only_pics, has_text, pic_part, text_without_picids
async def build_keywords_reaction_prompt(self, target: Optional[str]) -> str:
"""构建关键词反应提示
@ -582,7 +636,12 @@ class DefaultReplyer:
target = reply_message.processed_plain_text
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
target = re.sub(r"\\[picid:[^\\]]+\\]", "[图片]", target)
# 在picid替换之前分析内容类型防止prompt注入
has_only_pics, has_text, pic_part, text_part = self._analyze_target_content(target)
# 将[picid:xxx]替换为具体的图片描述
target = self._replace_picids_with_descriptions(target)
message_list_before_now_long = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_id,
@ -695,10 +754,19 @@ class DefaultReplyer:
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
if sender:
if is_group_chat:
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意"
else: # private chat
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意"
# 使用预先分析的内容类型结果
if has_only_pics and not has_text:
# 只包含图片
reply_target_block = f"现在{sender}发送的图片:{pic_part}。引起了你的注意"
elif has_text and pic_part:
# 既有图片又有文字
reply_target_block = f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意"
elif has_text:
# 只包含文字
reply_target_block = f"现在{sender}说的:{text_part}。引起了你的注意"
else:
# 其他情况(空内容等)
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意"
else:
reply_target_block = ""
@ -707,51 +775,28 @@ class DefaultReplyer:
message_list_before_now_long, user_id, sender
)
if global_config.bot.qq_account == user_id and platform == global_config.bot.platform:
return await global_prompt_manager.format_prompt(
"replyer_self_prompt",
expression_habits_block=expression_habits_block,
tool_info_block=tool_info,
memory_block=memory_block,
knowledge_prompt=prompt_info,
mood_state=mood_state_prompt,
# memory_block=memory_block,
# relation_info_block=relation_info,
extra_info_block=extra_info_block,
identity=personality_prompt,
action_descriptions=actions_info,
background_dialogue_prompt=background_dialogue_prompt,
time_block=time_block,
target=target,
reason=reply_reason,
reply_style=global_config.personality.reply_style,
question_block=question_block,
keywords_reaction_prompt=keywords_reaction_prompt,
moderation_prompt=moderation_prompt_block,
), selected_expressions
else:
return await global_prompt_manager.format_prompt(
"replyer_prompt",
expression_habits_block=expression_habits_block,
tool_info_block=tool_info,
memory_block=memory_block,
knowledge_prompt=prompt_info,
mood_state=mood_state_prompt,
# memory_block=memory_block,
# relation_info_block=relation_info,
extra_info_block=extra_info_block,
identity=personality_prompt,
action_descriptions=actions_info,
sender_name=sender,
background_dialogue_prompt=background_dialogue_prompt,
time_block=time_block,
core_dialogue_prompt=core_dialogue_prompt,
reply_target_block=reply_target_block,
reply_style=global_config.personality.reply_style,
keywords_reaction_prompt=keywords_reaction_prompt,
moderation_prompt=moderation_prompt_block,
question_block=question_block,
), selected_expressions
return await global_prompt_manager.format_prompt(
"replyer_prompt",
expression_habits_block=expression_habits_block,
tool_info_block=tool_info,
memory_block=memory_block,
knowledge_prompt=prompt_info,
mood_state=mood_state_prompt,
# memory_block=memory_block,
# relation_info_block=relation_info,
extra_info_block=extra_info_block,
identity=personality_prompt,
action_descriptions=actions_info,
sender_name=sender,
background_dialogue_prompt=background_dialogue_prompt,
time_block=time_block,
core_dialogue_prompt=core_dialogue_prompt,
reply_target_block=reply_target_block,
reply_style=global_config.personality.reply_style,
keywords_reaction_prompt=keywords_reaction_prompt,
moderation_prompt=moderation_prompt_block,
question_block=question_block,
), selected_expressions
async def build_prompt_rewrite_context(
self,
@ -765,7 +810,12 @@ class DefaultReplyer:
sender, target = self._parse_reply_target(reply_to)
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
target = re.sub(r"\\[picid:[^\\]]+\\]", "[图片]", target)
# 在picid替换之前分析内容类型防止prompt注入
has_only_pics, has_text, pic_part, text_part = self._analyze_target_content(target)
# 将[picid:xxx]替换为具体的图片描述
target = self._replace_picids_with_descriptions(target)
message_list_before_now_half = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_id,
@ -795,18 +845,39 @@ class DefaultReplyer:
)
if sender and target:
# 使用预先分析的内容类型结果
if is_group_chat:
if sender:
reply_target_block = (
f"现在{sender}说的:{target}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
if has_only_pics and not has_text:
# 只包含图片
reply_target_block = (
f"现在{sender}发送的图片:{pic_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif has_text and pic_part:
# 既有图片又有文字
reply_target_block = (
f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
else:
# 只包含文字
reply_target_block = (
f"现在{sender}说的:{text_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif target:
reply_target_block = f"现在{target}引起了你的注意,你想要在群里发言或者回复这条消息。"
else:
reply_target_block = "现在,你想要在群里发言或者回复消息。"
else: # private chat
if sender:
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意,针对这条消息回复。"
if has_only_pics and not has_text:
# 只包含图片
reply_target_block = f"现在{sender}发送的图片:{pic_part}。引起了你的注意,针对这条消息回复。"
elif has_text and pic_part:
# 既有图片又有文字
reply_target_block = f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意,针对这条消息回复。"
else:
# 只包含文字
reply_target_block = f"现在{sender}说的:{text_part}。引起了你的注意,针对这条消息回复。"
elif target:
reply_target_block = f"现在{target}引起了你的注意,针对这条消息回复。"
else:

View File

@ -25,6 +25,7 @@ from src.chat.utils.chat_message_builder import (
replace_user_references,
)
from src.chat.express.expression_selector import expression_selector
from src.plugin_system.apis.message_api import translate_pid_to_description
from src.mood.mood_manager import mood_manager
# from src.memory_system.memory_activator import MemoryActivator
@ -362,6 +363,59 @@ class PrivateReplyer:
target = parts[1].strip()
return sender, target
def _replace_picids_with_descriptions(self, text: str) -> str:
"""将文本中的[picid:xxx]替换为具体的图片描述
Args:
text: 包含picid标记的文本
Returns:
替换后的文本
"""
# 匹配 [picid:xxxxx] 格式
pic_pattern = r"\[picid:([^\]]+)\]"
def replace_pic_id(match: re.Match) -> str:
pic_id = match.group(1)
description = translate_pid_to_description(pic_id)
return f"[图片:{description}]"
return re.sub(pic_pattern, replace_pic_id, text)
def _analyze_target_content(self, target: str) -> Tuple[bool, bool, str, str]:
"""分析target内容类型基于原始picid格式
Args:
target: 目标消息内容包含[picid:xxx]格式
Returns:
Tuple[bool, bool, str, str]: (是否只包含图片, 是否包含文字, 图片部分, 文字部分)
"""
if not target or not target.strip():
return False, False, "", ""
# 检查是否只包含picid标记
picid_pattern = r"\[picid:[^\]]+\]"
picid_matches = re.findall(picid_pattern, target)
# 移除所有picid标记后检查是否还有文字内容
text_without_picids = re.sub(picid_pattern, "", target).strip()
has_only_pics = len(picid_matches) > 0 and not text_without_picids
has_text = bool(text_without_picids)
# 提取图片部分(转换为[图片:描述]格式)
pic_part = ""
if picid_matches:
pic_descriptions = []
for picid_match in picid_matches:
pic_id = picid_match[6:-1] # 提取picid:xxx中的xxx部分
description = translate_pid_to_description(pic_id)
pic_descriptions.append(f"[图片:{description}]")
pic_part = "".join(pic_descriptions)
return has_only_pics, has_text, pic_part, text_without_picids
async def build_keywords_reaction_prompt(self, target: Optional[str]) -> str:
"""构建关键词反应提示
@ -510,7 +564,12 @@ class PrivateReplyer:
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
target = re.sub(r"\\[picid:[^\\]]+\\]", "[图片]", target)
# 在picid替换之前分析内容类型防止prompt注入
has_only_pics, has_text, pic_part, text_part = self._analyze_target_content(target)
# 将[picid:xxx]替换为具体的图片描述
target = self._replace_picids_with_descriptions(target)
message_list_before_now_long = get_raw_msg_before_timestamp_with_chat(
chat_id=chat_id,
@ -629,9 +688,19 @@ class PrivateReplyer:
moderation_prompt_block = "请不要输出违法违规内容,不要输出色情,暴力,政治相关内容,如有敏感内容,请规避。"
reply_target_block = (
f"现在对方说的:{target}。引起了你的注意"
)
# 使用预先分析的内容类型结果
if has_only_pics and not has_text:
# 只包含图片
reply_target_block = f"现在对方发送的图片:{pic_part}。引起了你的注意"
elif has_text and pic_part:
# 既有图片又有文字
reply_target_block = f"现在对方发送了图片:{pic_part},并说:{text_part}。引起了你的注意"
elif has_text:
# 只包含文字
reply_target_block = f"现在对方说的:{text_part}。引起了你的注意"
else:
# 其他情况(空内容等)
reply_target_block = f"现在对方说的:{target}。引起了你的注意"
if global_config.bot.qq_account == user_id and platform == global_config.bot.platform:
return await global_prompt_manager.format_prompt(
@ -687,7 +756,12 @@ class PrivateReplyer:
sender, target = self._parse_reply_target(reply_to)
target = replace_user_references(target, chat_stream.platform, replace_bot_name=True)
target = re.sub(r"\\[picid:[^\\]]+\\]", "[图片]", target)
# 在picid替换之前分析内容类型防止prompt注入
has_only_pics, has_text, pic_part, text_part = self._analyze_target_content(target)
# 将[picid:xxx]替换为具体的图片描述
target = self._replace_picids_with_descriptions(target)
@ -720,18 +794,39 @@ class PrivateReplyer:
)
if sender and target:
# 使用预先分析的内容类型结果
if is_group_chat:
if sender:
reply_target_block = (
f"现在{sender}说的:{target}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
if has_only_pics and not has_text:
# 只包含图片
reply_target_block = (
f"现在{sender}发送的图片:{pic_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif has_text and pic_part:
# 既有图片又有文字
reply_target_block = (
f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
else:
# 只包含文字
reply_target_block = (
f"现在{sender}说的:{text_part}。引起了你的注意,你想要在群里发言或者回复这条消息。"
)
elif target:
reply_target_block = f"现在{target}引起了你的注意,你想要在群里发言或者回复这条消息。"
else:
reply_target_block = "现在,你想要在群里发言或者回复消息。"
else: # private chat
if sender:
reply_target_block = f"现在{sender}说的:{target}。引起了你的注意,针对这条消息回复。"
if has_only_pics and not has_text:
# 只包含图片
reply_target_block = f"现在{sender}发送的图片:{pic_part}。引起了你的注意,针对这条消息回复。"
elif has_text and pic_part:
# 既有图片又有文字
reply_target_block = f"现在{sender}发送了图片:{pic_part},并说:{text_part}。引起了你的注意,针对这条消息回复。"
else:
# 只包含文字
reply_target_block = f"现在{sender}说的:{text_part}。引起了你的注意,针对这条消息回复。"
elif target:
reply_target_block = f"现在{target}引起了你的注意,针对这条消息回复。"
else:

View File

@ -25,32 +25,11 @@ def init_replyer_prompt():
你正在群里聊天,现在请你读读之前的聊天记录然后给出日常且口语化的回复平淡一些{mood_state}
尽量简短一些{keywords_reaction_prompt}请注意把握聊天内容不要回复的太有条理可以有个性
{reply_style}
请注意不要输出多余内容(包括前后缀冒号和引号括号表情等)只输出回复内容
{moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )""",
请注意不要输出多余内容(包括前后缀冒号和引号括号表情等)只输出一句回复内容就好
{moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )
现在你说""",
"replyer_prompt",
)
Prompt(
"""{knowledge_prompt}{tool_info_block}{extra_info_block}
{expression_habits_block}{memory_block}{question_block}
你正在qq群里聊天下面是群里正在聊的内容:
{time_block}
{background_dialogue_prompt}
你现在想补充说明你刚刚自己的发言内容{target}原因是{reason}
请你根据聊天内容组织一条新回复注意{target} 是刚刚你自己的发言你要在这基础上进一步发言请按照你自己的角度来继续进行回复注意保持上下文的连贯性{mood_state}
{identity}
尽量简短一些{keywords_reaction_prompt}请注意把握聊天内容不要回复的太有条理可以有个性
{reply_style}
请注意不要输出多余内容(包括前后缀冒号和引号括号表情等)只输出回复内容
{moderation_prompt}不要输出多余内容(包括前后缀冒号和引号括号表情包at或 @等 )
""",
"replyer_self_prompt",
)
Prompt(

View File

@ -10,10 +10,10 @@ class QuestionMaker:
self.chat_id = chat_id
self.context = context
def get_context(self):
def get_context(self,timestamp: float = time.time()):
latest_30_msgs = get_raw_msg_before_timestamp_with_chat(
chat_id=self.chat_id,
timestamp=time.time(),
timestamp=timestamp,
limit=30,
)
@ -42,9 +42,10 @@ class QuestionMaker:
async def make_question(self):
conflict = await self.get_random_unanswered_conflict()
if not conflict:
return None, None
return None, None, None
question = conflict.conflict_content
conflict_context = conflict.context
chat_context = self.get_context()
create_time = conflict.create_time
chat_context = self.get_context(create_time)
return question, conflict_context
return question, chat_context, conflict_context

View File

@ -77,8 +77,8 @@ class RelationActionsPlugin(BasePlugin):
# 配置Schema定义
config_schema: dict = {
"plugin": {
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"config_version": ConfigField(type=str, default="1.0.0", description="配置文件版本"),
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
"config_version": ConfigField(type=str, default="1.0.1", description="配置文件版本"),
},
"components": {
"relation_max_memory_num": ConfigField(type=int, default=10, description="关系记忆最大数量"),