feat:精简表达选择,优化replyer表现,优化缓存命中率
This commit is contained in:
@@ -268,6 +268,11 @@ class MaisakaExpressionSelector:
|
||||
reply_reason: str,
|
||||
sub_agent_runner: Optional[SubAgentRunner],
|
||||
) -> MaisakaExpressionSelectionResult:
|
||||
del chat_history
|
||||
del reply_message
|
||||
del reply_reason
|
||||
del sub_agent_runner
|
||||
|
||||
if not session_id:
|
||||
logger.info("表达方式选择已跳过:缺少 session_id")
|
||||
return MaisakaExpressionSelectionResult()
|
||||
@@ -280,48 +285,10 @@ class MaisakaExpressionSelector:
|
||||
logger.info(f"表达方式选择已跳过:本地候选不足,session_id={session_id}")
|
||||
return MaisakaExpressionSelectionResult()
|
||||
|
||||
if not global_config.expression.advanced_chosen:
|
||||
return self._build_direct_selection_result(
|
||||
session_id=session_id,
|
||||
candidates=candidates,
|
||||
)
|
||||
|
||||
if sub_agent_runner is None:
|
||||
logger.info(f"表达方式选择已跳过:缺少 sub_agent_runner,session_id={session_id}")
|
||||
return MaisakaExpressionSelectionResult()
|
||||
|
||||
logger.info(
|
||||
f"表达方式选择开始:session_id={session_id} 候选数={len(candidates)} "
|
||||
f"候选预览={self._format_candidate_preview(candidates)}"
|
||||
)
|
||||
|
||||
selector_prompt = self._build_selector_prompt(
|
||||
chat_history=chat_history,
|
||||
reply_message=reply_message,
|
||||
reply_reason=reply_reason,
|
||||
return self._build_direct_selection_result(
|
||||
session_id=session_id,
|
||||
candidates=candidates,
|
||||
)
|
||||
try:
|
||||
raw_response = await sub_agent_runner(selector_prompt)
|
||||
except Exception:
|
||||
logger.exception("表达方式选择子代理执行失败")
|
||||
return MaisakaExpressionSelectionResult()
|
||||
|
||||
selected_ids = self._parse_selected_ids(raw_response, candidates)
|
||||
if not selected_ids:
|
||||
logger.info(f"表达方式选择完成但未命中,session_id={session_id}")
|
||||
return MaisakaExpressionSelectionResult()
|
||||
|
||||
selected_expressions = [candidate for candidate in candidates if candidate.get("id") in selected_ids]
|
||||
self._update_last_active_time(selected_ids)
|
||||
logger.info(
|
||||
f"表达方式选择完成:session_id={session_id} 已选数={len(selected_ids)} "
|
||||
f"selected_ids={selected_ids!r} 已选预览={self._format_candidate_preview(selected_expressions)}"
|
||||
)
|
||||
return MaisakaExpressionSelectionResult(
|
||||
expression_habits=self._build_expression_habits_block(selected_expressions),
|
||||
selected_expression_ids=selected_ids,
|
||||
)
|
||||
|
||||
|
||||
maisaka_expression_selector = MaisakaExpressionSelector()
|
||||
|
||||
@@ -18,6 +18,14 @@ from src.common.data_models.reply_generation_data_models import (
|
||||
ReplyGenerationResult,
|
||||
build_reply_monitor_detail,
|
||||
)
|
||||
from src.common.data_models.message_component_data_model import (
|
||||
AtComponent,
|
||||
EmojiComponent,
|
||||
ImageComponent,
|
||||
ReplyComponent,
|
||||
TextComponent,
|
||||
VoiceComponent,
|
||||
)
|
||||
from src.common.logger import get_logger
|
||||
from src.common.utils.utils_session import SessionUtils
|
||||
from src.config.config import global_config
|
||||
@@ -100,6 +108,30 @@ class BaseMaisakaReplyGenerator:
|
||||
del message
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _strip_guided_reply_formatting(content: str) -> str:
|
||||
"""移除 guided_reply 可见文本中的引用包装,仅保留真正回复正文。"""
|
||||
|
||||
normalized_content = content.strip()
|
||||
if not normalized_content:
|
||||
return ""
|
||||
|
||||
reply_body_marker = "[发言内容]"
|
||||
if normalized_content.startswith("[引用]quote_id=") or normalized_content.startswith("[引用消息]"):
|
||||
marker_index = normalized_content.find(reply_body_marker)
|
||||
if marker_index >= 0:
|
||||
return normalized_content[marker_index + len(reply_body_marker) :].strip()
|
||||
|
||||
newline_index = normalized_content.find("\n")
|
||||
if newline_index < 0:
|
||||
return ""
|
||||
normalized_content = normalized_content[newline_index + 1 :].lstrip()
|
||||
|
||||
if normalized_content.startswith(reply_body_marker):
|
||||
normalized_content = normalized_content[len(reply_body_marker) :].lstrip()
|
||||
|
||||
return normalized_content.strip()
|
||||
|
||||
def _extract_guided_bot_reply(self, message: SessionBackedMessage) -> str:
|
||||
# 只能根据结构化来源字段判断是否为 bot 自身写回的历史消息,
|
||||
# 不能依赖昵称/群名片等可控文本,避免误判和提示注入。
|
||||
@@ -108,7 +140,9 @@ class BaseMaisakaReplyGenerator:
|
||||
|
||||
plain_text = message.processed_plain_text.strip()
|
||||
_, body = parse_speaker_content(plain_text)
|
||||
normalized_body = body.strip() or plain_text
|
||||
normalized_body = self._strip_guided_reply_formatting(body) or self._strip_guided_reply_formatting(
|
||||
plain_text
|
||||
)
|
||||
return self._normalize_content(normalized_body) if normalized_body else ""
|
||||
|
||||
def _build_target_message_block(self, reply_message: Optional[SessionMessage]) -> str:
|
||||
@@ -118,18 +152,62 @@ class BaseMaisakaReplyGenerator:
|
||||
user_info = reply_message.message_info.user_info
|
||||
sender_name = user_info.user_cardname or user_info.user_nickname or user_info.user_id
|
||||
target_message_id = reply_message.message_id.strip() if reply_message.message_id else "未知"
|
||||
target_content = self._normalize_content((reply_message.processed_plain_text or "").strip(), limit=300)
|
||||
target_time = reply_message.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
target_content = self._normalize_content(self._build_target_message_content(reply_message), limit=300)
|
||||
if not target_content:
|
||||
target_content = "[无可见文本内容]"
|
||||
|
||||
return (
|
||||
"【本次回复目标】\n"
|
||||
f"- 目标消息ID:{target_message_id}\n"
|
||||
f"- 发送者:{sender_name}\n"
|
||||
f"- 消息内容:{target_content}\n"
|
||||
"- 你这次要回复的就是这条目标消息,请结合整段上下文理解,但不要把其他历史消息当成当前回复对象。"
|
||||
f"- msg_id:{target_message_id}\n"
|
||||
f"- 时间:{target_time}\n"
|
||||
f"- 用户名:{sender_name}\n"
|
||||
f"- 发言内容:{target_content}\n\n"
|
||||
"你这次要回复的就是这条目标消息,请结合整段上下文理解,但不要把其他历史消息当成当前回复对象。"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _render_target_at_component(component: AtComponent) -> str:
|
||||
target_name = component.target_user_cardname or component.target_user_nickname or component.target_user_id
|
||||
return f"@{target_name}".strip()
|
||||
|
||||
def _build_target_message_content(self, reply_message: SessionMessage) -> str:
|
||||
rendered_parts: List[str] = []
|
||||
|
||||
for component in reply_message.raw_message.components:
|
||||
if isinstance(component, TextComponent):
|
||||
if component.text:
|
||||
rendered_parts.append(component.text)
|
||||
continue
|
||||
|
||||
if isinstance(component, ReplyComponent):
|
||||
target_message_id = component.target_message_id.strip()
|
||||
if target_message_id:
|
||||
rendered_parts.append(f"[引用:quote_id={target_message_id}]")
|
||||
continue
|
||||
|
||||
if isinstance(component, AtComponent):
|
||||
rendered_at = self._render_target_at_component(component)
|
||||
if rendered_at:
|
||||
rendered_parts.append(rendered_at)
|
||||
continue
|
||||
|
||||
if isinstance(component, ImageComponent):
|
||||
rendered_parts.append(component.content.strip() or "[图片]")
|
||||
continue
|
||||
|
||||
if isinstance(component, EmojiComponent):
|
||||
rendered_parts.append(component.content.strip() or "[表情包]")
|
||||
continue
|
||||
|
||||
if isinstance(component, VoiceComponent):
|
||||
rendered_parts.append(component.content.strip() or "[语音消息]")
|
||||
|
||||
normalized_content = " ".join(part.strip() for part in rendered_parts if part and part.strip()).strip()
|
||||
if normalized_content:
|
||||
return normalized_content
|
||||
return (reply_message.processed_plain_text or "").strip()
|
||||
|
||||
@staticmethod
|
||||
def _get_chat_prompt_for_chat(chat_id: str, is_group_chat: Optional[bool]) -> str:
|
||||
"""根据聊天流 ID 获取匹配的额外 prompt。"""
|
||||
@@ -221,8 +299,10 @@ class BaseMaisakaReplyGenerator:
|
||||
expression_habits: str = "",
|
||||
stream_id: Optional[str] = None,
|
||||
) -> str:
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
target_message_block = self._build_target_message_block(reply_message)
|
||||
del reply_message
|
||||
del reply_reason
|
||||
del reference_info
|
||||
del expression_habits
|
||||
session_id = self._resolve_session_id(stream_id)
|
||||
|
||||
try:
|
||||
@@ -231,16 +311,29 @@ class BaseMaisakaReplyGenerator:
|
||||
bot_name=global_config.bot.nickname,
|
||||
group_chat_attention_block=self._build_group_chat_attention_block(session_id),
|
||||
replyer_at_block=self._build_replyer_at_block(),
|
||||
time_block=f"当前时间:{current_time}",
|
||||
identity=self._personality_prompt,
|
||||
reply_style=global_config.personality.reply_style,
|
||||
)
|
||||
except Exception:
|
||||
system_prompt = "你是一个友好的 AI 助手,请根据聊天记录自然回复。"
|
||||
|
||||
sections: List[str] = []
|
||||
return system_prompt
|
||||
|
||||
def _build_reply_instruction(self) -> str:
|
||||
return "请自然地回复。不要输出多余说明、括号、@ 或额外标记,只输出实际要发送的内容。"
|
||||
|
||||
def _build_final_user_message(
|
||||
self,
|
||||
reply_message: Optional[SessionMessage],
|
||||
reply_reason: str,
|
||||
reference_info: str = "",
|
||||
expression_habits: str = "",
|
||||
) -> str:
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
sections: List[str] = [f"当前时间:{current_time}"]
|
||||
if expression_habits.strip():
|
||||
sections.append(expression_habits.strip())
|
||||
target_message_block = self._build_target_message_block(reply_message)
|
||||
if target_message_block:
|
||||
sections.append(target_message_block)
|
||||
reply_reference_lines: List[str] = []
|
||||
@@ -250,12 +343,8 @@ class BaseMaisakaReplyGenerator:
|
||||
reply_reference_lines.append(f"【参考信息】\n{reference_info.strip()}")
|
||||
if reply_reference_lines:
|
||||
sections.append("【回复信息参考】\n" + "\n\n".join(reply_reference_lines))
|
||||
if not sections:
|
||||
return system_prompt
|
||||
return f"{system_prompt}\n\n" + "\n\n".join(sections)
|
||||
|
||||
def _build_reply_instruction(self) -> str:
|
||||
return "请自然地回复。不要输出多余说明、括号、@ 或额外标记,只输出实际要发送的内容。"
|
||||
sections.append(self._build_reply_instruction())
|
||||
return "\n\n".join(sections)
|
||||
|
||||
def _build_history_messages(
|
||||
self,
|
||||
@@ -311,11 +400,16 @@ class BaseMaisakaReplyGenerator:
|
||||
expression_habits=expression_habits,
|
||||
stream_id=stream_id,
|
||||
)
|
||||
instruction = self._build_reply_instruction()
|
||||
final_user_message = self._build_final_user_message(
|
||||
reply_message=reply_message,
|
||||
reply_reason=reply_reason,
|
||||
reference_info=reference_info,
|
||||
expression_habits=expression_habits,
|
||||
)
|
||||
|
||||
messages.append(MessageBuilder().set_role(RoleType.System).add_text_content(system_prompt).build())
|
||||
messages.extend(self._build_history_messages(chat_history, enable_visual_message))
|
||||
messages.append(MessageBuilder().set_role(RoleType.User).add_text_content(instruction).build())
|
||||
messages.append(MessageBuilder().set_role(RoleType.User).add_text_content(final_user_message).build())
|
||||
return messages
|
||||
|
||||
def _resolve_enable_visual_message(self, model_info: Optional[ModelInfo] = None) -> bool:
|
||||
@@ -403,10 +497,10 @@ class BaseMaisakaReplyGenerator:
|
||||
result.error_message = "聊天历史为空"
|
||||
return finalize(False)
|
||||
|
||||
logger.info(
|
||||
f"Maisaka 回复器开始生成: 流={stream_id} 原因={reply_reason!r} "
|
||||
f"历史条数={len(chat_history)} 目标ID={reply_message.message_id if reply_message else None}"
|
||||
)
|
||||
# logger.info(
|
||||
# f"Maisaka 回复器开始生成: 流={stream_id} 原因={reply_reason!r} "
|
||||
# f"历史条数={len(chat_history)} 目标ID={reply_message.message_id if reply_message else None}"
|
||||
# )
|
||||
|
||||
filtered_history = [
|
||||
message
|
||||
@@ -444,9 +538,9 @@ class BaseMaisakaReplyGenerator:
|
||||
else list(reply_context.selected_expression_ids)
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"回复上下文完成 流={stream_id} 已选表达={result.selected_expression_ids!r}"
|
||||
)
|
||||
# logger.info(
|
||||
# f"回复上下文完成 流={stream_id} 已选表达={result.selected_expression_ids!r}"
|
||||
# )
|
||||
|
||||
prompt_started_at = time.perf_counter()
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user