feat:精简表达选择,优化replyer表现,优化缓存命中率
This commit is contained in:
@@ -1,10 +1,7 @@
|
|||||||
在下面的内容中,标注 {bot_name}(你) 的发言是你自己的发言,请注意区分:
|
|
||||||
{identity}
|
{identity}
|
||||||
{time_block}
|
|
||||||
|
|
||||||
现在请你读读之前的聊天记录,把握当前的话题,然后给出日常且口语化的回复,
|
现在请你读读之前的聊天记录,把握当前的话题,然后给出日常且口语化的回复,
|
||||||
{reply_style}
|
{reply_style}
|
||||||
你可以参考【回复信息参考】中的信息,但是视情况而定,不用完全遵守。
|
你可以参考【回复信息参考】中的信息,但是视情况而定,不用完全遵守。
|
||||||
{group_chat_attention_block}
|
{group_chat_attention_block}
|
||||||
{replyer_at_block}
|
{replyer_at_block}
|
||||||
请注意不要输出多余内容(包括不必要的前后缀,冒号,括号,表情包,普通 at 或 @等 ),只输出发言内容就好。
|
请注意不要输出多余内容(包括不必要的前后缀,冒号,括号,表情包,@等 ),只输出发言内容就好。
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
from src.common.data_models.message_component_data_model import ImageComponent, MessageSequence, TextComponent
|
from src.common.data_models.message_component_data_model import (
|
||||||
|
ImageComponent,
|
||||||
|
MessageSequence,
|
||||||
|
ReplyComponent,
|
||||||
|
TextComponent,
|
||||||
|
)
|
||||||
from src.llm_models.payload_content.message import RoleType
|
from src.llm_models.payload_content.message import RoleType
|
||||||
from src.maisaka.context_messages import _build_message_from_sequence
|
from src.maisaka.context_messages import _build_message_from_sequence
|
||||||
from src.maisaka.message_adapter import build_visible_text_from_sequence
|
from src.maisaka.message_adapter import build_visible_text_from_sequence
|
||||||
@@ -53,3 +58,16 @@ def test_visible_text_uses_image_placeholder_for_whitespace_content() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert visible_text == "看这个[图片]"
|
assert visible_text == "看这个[图片]"
|
||||||
|
|
||||||
|
|
||||||
|
def test_visible_text_adds_body_marker_after_reply_component() -> None:
|
||||||
|
visible_text = build_visible_text_from_sequence(
|
||||||
|
MessageSequence(
|
||||||
|
[
|
||||||
|
ReplyComponent(target_message_id="75625487"),
|
||||||
|
TextComponent("你说是那就是"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert visible_text == "[引用]quote_id=75625487\n[发言内容]你说是那就是"
|
||||||
|
|||||||
@@ -118,11 +118,16 @@ def test_legacy_replyer_builds_message_sequence_like_multimodal() -> None:
|
|||||||
|
|
||||||
assert len(request_messages) == 4
|
assert len(request_messages) == 4
|
||||||
assert request_messages[0].role.value == "system"
|
assert request_messages[0].role.value == "system"
|
||||||
|
assert request_messages[0].get_text_content() == "legacy prompt"
|
||||||
assert request_messages[1].role.value == "user"
|
assert request_messages[1].role.value == "user"
|
||||||
assert request_messages[1].get_text_content() == "[Alice]你好"
|
assert request_messages[1].get_text_content() == "[Alice]你好"
|
||||||
assert request_messages[2].role.value == "user"
|
assert request_messages[2].role.value == "user"
|
||||||
assert request_messages[2].get_text_content() == "[Bob]在吗"
|
assert request_messages[2].get_text_content() == "[Bob]在吗"
|
||||||
assert request_messages[3].role.value == "user"
|
assert request_messages[3].role.value == "user"
|
||||||
|
assert "当前时间:" in request_messages[3].get_text_content()
|
||||||
|
assert "【回复信息参考】" in request_messages[3].get_text_content()
|
||||||
|
assert "【最新推理】\n测试原因" in request_messages[3].get_text_content()
|
||||||
|
assert "请自然地回复。" in request_messages[3].get_text_content()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|||||||
@@ -268,6 +268,11 @@ class MaisakaExpressionSelector:
|
|||||||
reply_reason: str,
|
reply_reason: str,
|
||||||
sub_agent_runner: Optional[SubAgentRunner],
|
sub_agent_runner: Optional[SubAgentRunner],
|
||||||
) -> MaisakaExpressionSelectionResult:
|
) -> MaisakaExpressionSelectionResult:
|
||||||
|
del chat_history
|
||||||
|
del reply_message
|
||||||
|
del reply_reason
|
||||||
|
del sub_agent_runner
|
||||||
|
|
||||||
if not session_id:
|
if not session_id:
|
||||||
logger.info("表达方式选择已跳过:缺少 session_id")
|
logger.info("表达方式选择已跳过:缺少 session_id")
|
||||||
return MaisakaExpressionSelectionResult()
|
return MaisakaExpressionSelectionResult()
|
||||||
@@ -280,48 +285,10 @@ class MaisakaExpressionSelector:
|
|||||||
logger.info(f"表达方式选择已跳过:本地候选不足,session_id={session_id}")
|
logger.info(f"表达方式选择已跳过:本地候选不足,session_id={session_id}")
|
||||||
return MaisakaExpressionSelectionResult()
|
return MaisakaExpressionSelectionResult()
|
||||||
|
|
||||||
if not global_config.expression.advanced_chosen:
|
return self._build_direct_selection_result(
|
||||||
return self._build_direct_selection_result(
|
session_id=session_id,
|
||||||
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,
|
|
||||||
candidates=candidates,
|
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()
|
maisaka_expression_selector = MaisakaExpressionSelector()
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ from src.common.data_models.reply_generation_data_models import (
|
|||||||
ReplyGenerationResult,
|
ReplyGenerationResult,
|
||||||
build_reply_monitor_detail,
|
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.logger import get_logger
|
||||||
from src.common.utils.utils_session import SessionUtils
|
from src.common.utils.utils_session import SessionUtils
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
@@ -100,6 +108,30 @@ class BaseMaisakaReplyGenerator:
|
|||||||
del message
|
del message
|
||||||
return ""
|
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:
|
def _extract_guided_bot_reply(self, message: SessionBackedMessage) -> str:
|
||||||
# 只能根据结构化来源字段判断是否为 bot 自身写回的历史消息,
|
# 只能根据结构化来源字段判断是否为 bot 自身写回的历史消息,
|
||||||
# 不能依赖昵称/群名片等可控文本,避免误判和提示注入。
|
# 不能依赖昵称/群名片等可控文本,避免误判和提示注入。
|
||||||
@@ -108,7 +140,9 @@ class BaseMaisakaReplyGenerator:
|
|||||||
|
|
||||||
plain_text = message.processed_plain_text.strip()
|
plain_text = message.processed_plain_text.strip()
|
||||||
_, body = parse_speaker_content(plain_text)
|
_, 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 ""
|
return self._normalize_content(normalized_body) if normalized_body else ""
|
||||||
|
|
||||||
def _build_target_message_block(self, reply_message: Optional[SessionMessage]) -> str:
|
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
|
user_info = reply_message.message_info.user_info
|
||||||
sender_name = user_info.user_cardname or user_info.user_nickname or user_info.user_id
|
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_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:
|
if not target_content:
|
||||||
target_content = "[无可见文本内容]"
|
target_content = "[无可见文本内容]"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
"【本次回复目标】\n"
|
"【本次回复目标】\n"
|
||||||
f"- 目标消息ID:{target_message_id}\n"
|
f"- msg_id:{target_message_id}\n"
|
||||||
f"- 发送者:{sender_name}\n"
|
f"- 时间:{target_time}\n"
|
||||||
f"- 消息内容:{target_content}\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
|
@staticmethod
|
||||||
def _get_chat_prompt_for_chat(chat_id: str, is_group_chat: Optional[bool]) -> str:
|
def _get_chat_prompt_for_chat(chat_id: str, is_group_chat: Optional[bool]) -> str:
|
||||||
"""根据聊天流 ID 获取匹配的额外 prompt。"""
|
"""根据聊天流 ID 获取匹配的额外 prompt。"""
|
||||||
@@ -221,8 +299,10 @@ class BaseMaisakaReplyGenerator:
|
|||||||
expression_habits: str = "",
|
expression_habits: str = "",
|
||||||
stream_id: Optional[str] = None,
|
stream_id: Optional[str] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
del reply_message
|
||||||
target_message_block = self._build_target_message_block(reply_message)
|
del reply_reason
|
||||||
|
del reference_info
|
||||||
|
del expression_habits
|
||||||
session_id = self._resolve_session_id(stream_id)
|
session_id = self._resolve_session_id(stream_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -231,16 +311,29 @@ class BaseMaisakaReplyGenerator:
|
|||||||
bot_name=global_config.bot.nickname,
|
bot_name=global_config.bot.nickname,
|
||||||
group_chat_attention_block=self._build_group_chat_attention_block(session_id),
|
group_chat_attention_block=self._build_group_chat_attention_block(session_id),
|
||||||
replyer_at_block=self._build_replyer_at_block(),
|
replyer_at_block=self._build_replyer_at_block(),
|
||||||
time_block=f"当前时间:{current_time}",
|
|
||||||
identity=self._personality_prompt,
|
identity=self._personality_prompt,
|
||||||
reply_style=global_config.personality.reply_style,
|
reply_style=global_config.personality.reply_style,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
system_prompt = "你是一个友好的 AI 助手,请根据聊天记录自然回复。"
|
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():
|
if expression_habits.strip():
|
||||||
sections.append(expression_habits.strip())
|
sections.append(expression_habits.strip())
|
||||||
|
target_message_block = self._build_target_message_block(reply_message)
|
||||||
if target_message_block:
|
if target_message_block:
|
||||||
sections.append(target_message_block)
|
sections.append(target_message_block)
|
||||||
reply_reference_lines: List[str] = []
|
reply_reference_lines: List[str] = []
|
||||||
@@ -250,12 +343,8 @@ class BaseMaisakaReplyGenerator:
|
|||||||
reply_reference_lines.append(f"【参考信息】\n{reference_info.strip()}")
|
reply_reference_lines.append(f"【参考信息】\n{reference_info.strip()}")
|
||||||
if reply_reference_lines:
|
if reply_reference_lines:
|
||||||
sections.append("【回复信息参考】\n" + "\n\n".join(reply_reference_lines))
|
sections.append("【回复信息参考】\n" + "\n\n".join(reply_reference_lines))
|
||||||
if not sections:
|
sections.append(self._build_reply_instruction())
|
||||||
return system_prompt
|
return "\n\n".join(sections)
|
||||||
return f"{system_prompt}\n\n" + "\n\n".join(sections)
|
|
||||||
|
|
||||||
def _build_reply_instruction(self) -> str:
|
|
||||||
return "请自然地回复。不要输出多余说明、括号、@ 或额外标记,只输出实际要发送的内容。"
|
|
||||||
|
|
||||||
def _build_history_messages(
|
def _build_history_messages(
|
||||||
self,
|
self,
|
||||||
@@ -311,11 +400,16 @@ class BaseMaisakaReplyGenerator:
|
|||||||
expression_habits=expression_habits,
|
expression_habits=expression_habits,
|
||||||
stream_id=stream_id,
|
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.append(MessageBuilder().set_role(RoleType.System).add_text_content(system_prompt).build())
|
||||||
messages.extend(self._build_history_messages(chat_history, enable_visual_message))
|
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
|
return messages
|
||||||
|
|
||||||
def _resolve_enable_visual_message(self, model_info: Optional[ModelInfo] = None) -> bool:
|
def _resolve_enable_visual_message(self, model_info: Optional[ModelInfo] = None) -> bool:
|
||||||
@@ -403,10 +497,10 @@ class BaseMaisakaReplyGenerator:
|
|||||||
result.error_message = "聊天历史为空"
|
result.error_message = "聊天历史为空"
|
||||||
return finalize(False)
|
return finalize(False)
|
||||||
|
|
||||||
logger.info(
|
# logger.info(
|
||||||
f"Maisaka 回复器开始生成: 流={stream_id} 原因={reply_reason!r} "
|
# f"Maisaka 回复器开始生成: 流={stream_id} 原因={reply_reason!r} "
|
||||||
f"历史条数={len(chat_history)} 目标ID={reply_message.message_id if reply_message else None}"
|
# f"历史条数={len(chat_history)} 目标ID={reply_message.message_id if reply_message else None}"
|
||||||
)
|
# )
|
||||||
|
|
||||||
filtered_history = [
|
filtered_history = [
|
||||||
message
|
message
|
||||||
@@ -444,9 +538,9 @@ class BaseMaisakaReplyGenerator:
|
|||||||
else list(reply_context.selected_expression_ids)
|
else list(reply_context.selected_expression_ids)
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
# logger.info(
|
||||||
f"回复上下文完成 流={stream_id} 已选表达={result.selected_expression_ids!r}"
|
# f"回复上下文完成 流={stream_id} 已选表达={result.selected_expression_ids!r}"
|
||||||
)
|
# )
|
||||||
|
|
||||||
prompt_started_at = time.perf_counter()
|
prompt_started_at = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -720,15 +720,6 @@ class ExpressionConfig(ConfigBase):
|
|||||||
)
|
)
|
||||||
"""_wrap_表达学习配置列表,支持按聊天流配置"""
|
"""_wrap_表达学习配置列表,支持按聊天流配置"""
|
||||||
|
|
||||||
advanced_chosen: bool = Field(
|
|
||||||
default=False,
|
|
||||||
json_schema_extra={
|
|
||||||
"x-widget": "switch",
|
|
||||||
"x-icon": "sparkles",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
"""是否启用基于子代理的二次表达方式选择"""
|
|
||||||
|
|
||||||
expression_groups: list[ExpressionGroup] = Field(
|
expression_groups: list[ExpressionGroup] = Field(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def _append_reply_component(builder: MessageBuilder, component: ReplyComponent)
|
|||||||
if not target_message_id:
|
if not target_message_id:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
builder.add_text_content(f"[引用]quote_id={target_message_id}")
|
builder.add_text_content(f"[引用消息]{target_message_id}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ def _render_component_for_prompt(component: StandardMessageComponents) -> str:
|
|||||||
if target_content:
|
if target_content:
|
||||||
return f"[回复消息: {target_content}]"
|
return f"[回复消息: {target_content}]"
|
||||||
target_message_id = component.target_message_id.strip()
|
target_message_id = component.target_message_id.strip()
|
||||||
return f"[引用]quote_id={target_message_id}" if target_message_id else "[回复消息]"
|
return f"[引用消息]{target_message_id}" if target_message_id else "[回复消息]"
|
||||||
|
|
||||||
if isinstance(component, ForwardNodeComponent):
|
if isinstance(component, ForwardNodeComponent):
|
||||||
return _build_forward_preview_block(component)
|
return _build_forward_preview_block(component)
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from .context_messages import AssistantMessage, LLMContextMessage, ToolResultMessage
|
from .context_messages import AssistantMessage, LLMContextMessage
|
||||||
from .history_utils import drop_leading_orphan_tool_results, drop_orphan_tool_results, normalize_tool_result_order
|
from .history_utils import drop_leading_orphan_tool_results, drop_orphan_tool_results, normalize_tool_result_order
|
||||||
|
|
||||||
TIMING_HISTORY_TOOL_NAMES = {"continue", "finish", "no_reply", "wait"}
|
|
||||||
EARLY_TRIM_RATIO = 0.2
|
EARLY_TRIM_RATIO = 0.2
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ def process_chat_history_after_cycle(
|
|||||||
"""在每轮结束后统一执行历史裁切与清理。"""
|
"""在每轮结束后统一执行历史裁切与清理。"""
|
||||||
|
|
||||||
processed_history = list(chat_history)
|
processed_history = list(chat_history)
|
||||||
removed_timing_tool_count = _remove_early_timing_tool_records(processed_history)
|
|
||||||
removed_assistant_thought_count = _remove_early_assistant_thoughts(processed_history)
|
removed_assistant_thought_count = _remove_early_assistant_thoughts(processed_history)
|
||||||
|
|
||||||
processed_history, orphan_removed_count = drop_orphan_tool_results(processed_history)
|
processed_history, orphan_removed_count = drop_orphan_tool_results(processed_history)
|
||||||
@@ -45,8 +43,7 @@ def process_chat_history_after_cycle(
|
|||||||
removed_overflow_count += leading_orphan_removed_count
|
removed_overflow_count += leading_orphan_removed_count
|
||||||
remaining_context_count = sum(1 for message in processed_history if message.count_in_context)
|
remaining_context_count = sum(1 for message in processed_history if message.count_in_context)
|
||||||
removed_count = (
|
removed_count = (
|
||||||
removed_timing_tool_count
|
removed_assistant_thought_count
|
||||||
+ removed_assistant_thought_count
|
|
||||||
+ orphan_removed_count
|
+ orphan_removed_count
|
||||||
+ removed_overflow_count
|
+ removed_overflow_count
|
||||||
)
|
)
|
||||||
@@ -59,41 +56,6 @@ def process_chat_history_after_cycle(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _remove_early_timing_tool_records(chat_history: list[LLMContextMessage]) -> int:
|
|
||||||
"""移除最早 20% 的门控/结束类工具链记录。"""
|
|
||||||
|
|
||||||
candidate_assistant_indexes = [
|
|
||||||
index
|
|
||||||
for index, message in enumerate(chat_history)
|
|
||||||
if _is_timing_tool_assistant_message(message)
|
|
||||||
]
|
|
||||||
remove_count = int(len(candidate_assistant_indexes) * EARLY_TRIM_RATIO)
|
|
||||||
if remove_count <= 0:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
removed_indexes = set(candidate_assistant_indexes[:remove_count])
|
|
||||||
removed_tool_call_ids = {
|
|
||||||
tool_call.call_id
|
|
||||||
for index in removed_indexes
|
|
||||||
for tool_call in chat_history[index].tool_calls
|
|
||||||
if tool_call.call_id
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered_history: list[LLMContextMessage] = []
|
|
||||||
removed_total = 0
|
|
||||||
for index, message in enumerate(chat_history):
|
|
||||||
if index in removed_indexes:
|
|
||||||
removed_total += 1
|
|
||||||
continue
|
|
||||||
if isinstance(message, ToolResultMessage) and message.tool_call_id in removed_tool_call_ids:
|
|
||||||
removed_total += 1
|
|
||||||
continue
|
|
||||||
filtered_history.append(message)
|
|
||||||
|
|
||||||
chat_history[:] = filtered_history
|
|
||||||
return removed_total
|
|
||||||
|
|
||||||
|
|
||||||
def _remove_early_assistant_thoughts(chat_history: list[LLMContextMessage]) -> int:
|
def _remove_early_assistant_thoughts(chat_history: list[LLMContextMessage]) -> int:
|
||||||
"""移除最早 20% 的非工具 assistant 思考内容。"""
|
"""移除最早 20% 的非工具 assistant 思考内容。"""
|
||||||
|
|
||||||
@@ -122,8 +84,3 @@ def _remove_early_assistant_thoughts(chat_history: list[LLMContextMessage]) -> i
|
|||||||
return removed_total
|
return removed_total
|
||||||
|
|
||||||
|
|
||||||
def _is_timing_tool_assistant_message(message: LLMContextMessage) -> bool:
|
|
||||||
if not isinstance(message, AssistantMessage) or not message.tool_calls:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return all(tool_call.func_name in TIMING_HISTORY_TOOL_NAMES for tool_call in message.tool_calls)
|
|
||||||
|
|||||||
@@ -60,11 +60,23 @@ def build_visible_text_from_sequence(message_sequence: MessageSequence) -> str:
|
|||||||
"""从消息片段序列提取可见文本。"""
|
"""从消息片段序列提取可见文本。"""
|
||||||
|
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
|
pending_reply_body_prefix = False
|
||||||
|
|
||||||
|
def append_visible_part(text: str) -> None:
|
||||||
|
nonlocal pending_reply_body_prefix
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
if pending_reply_body_prefix:
|
||||||
|
parts.append(f"\n[发言内容]{text}")
|
||||||
|
pending_reply_body_prefix = False
|
||||||
|
return
|
||||||
|
parts.append(text)
|
||||||
|
|
||||||
for component in message_sequence.components:
|
for component in message_sequence.components:
|
||||||
if isinstance(component, TextComponent):
|
if isinstance(component, TextComponent):
|
||||||
match = SPEAKER_PREFIX_PATTERN.match(component.text or "")
|
match = SPEAKER_PREFIX_PATTERN.match(component.text or "")
|
||||||
if not match:
|
if not match:
|
||||||
parts.append(component.text)
|
append_visible_part(component.text)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
normalized_parts: list[str] = []
|
normalized_parts: list[str] = []
|
||||||
@@ -75,24 +87,25 @@ def build_visible_text_from_sequence(message_sequence: MessageSequence) -> str:
|
|||||||
normalized_parts.append(f"[msg_id:{message_id}]")
|
normalized_parts.append(f"[msg_id:{message_id}]")
|
||||||
normalized_parts.append(f"[{match.group('speaker')}]")
|
normalized_parts.append(f"[{match.group('speaker')}]")
|
||||||
normalized_parts.append(match.group("content"))
|
normalized_parts.append(match.group("content"))
|
||||||
parts.append("".join(normalized_parts))
|
append_visible_part("".join(normalized_parts))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(component, EmojiComponent):
|
if isinstance(component, EmojiComponent):
|
||||||
parts.append(component.content.strip() or "[表情包]")
|
append_visible_part(component.content.strip() or "[表情包]")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(component, ImageComponent):
|
if isinstance(component, ImageComponent):
|
||||||
parts.append(component.content.strip() or "[图片]")
|
append_visible_part(component.content.strip() or "[图片]")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(component, AtComponent):
|
if isinstance(component, AtComponent):
|
||||||
parts.append(_render_at_component_text(component))
|
append_visible_part(_render_at_component_text(component))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(component, ReplyComponent):
|
if isinstance(component, ReplyComponent):
|
||||||
target_message_id = component.target_message_id.strip()
|
target_message_id = component.target_message_id.strip()
|
||||||
if target_message_id:
|
if target_message_id:
|
||||||
parts.append(f"[引用]quote_id={target_message_id}")
|
parts.append(f"[引用消息]{target_message_id}")
|
||||||
|
pending_reply_body_prefix = True
|
||||||
|
|
||||||
return "".join(parts)
|
return "".join(parts)
|
||||||
|
|||||||
@@ -273,10 +273,14 @@ async def _broadcast(event: str, data: Dict[str, Any]) -> None:
|
|||||||
for connection in websocket_manager.connections.values()
|
for connection in websocket_manager.connections.values()
|
||||||
if subscription_key in connection.subscriptions
|
if subscription_key in connection.subscriptions
|
||||||
)
|
)
|
||||||
logger.info(
|
# The above code is using the Python logging module to log a diagnostic message. It is logging
|
||||||
f"[诊断] _broadcast: manager_id={id(websocket_manager)} "
|
# information about the `_broadcast` function, including the `manager_id`, `total_connections`,
|
||||||
f"总连接={total_connections} 订阅者={subscriber_count} event={event}"
|
# `subscriber_count`, and `event` variables. The `logger.info()` function is used to log the message
|
||||||
)
|
# at the INFO level.
|
||||||
|
# logger.info(
|
||||||
|
# f"[诊断] _broadcast: manager_id={id(websocket_manager)} "
|
||||||
|
# f"总连接={total_connections} 订阅者={subscriber_count} event={event}"
|
||||||
|
# )
|
||||||
await websocket_manager.broadcast_to_topic(
|
await websocket_manager.broadcast_to_topic(
|
||||||
domain=MONITOR_DOMAIN,
|
domain=MONITOR_DOMAIN,
|
||||||
topic=MONITOR_TOPIC,
|
topic=MONITOR_TOPIC,
|
||||||
|
|||||||
@@ -478,11 +478,11 @@ class MaisakaReasoningEngine:
|
|||||||
)
|
)
|
||||||
planner_duration_ms = (time.time() - planner_started_at) * 1000
|
planner_duration_ms = (time.time() - planner_started_at) * 1000
|
||||||
cycle_detail.time_records["planner"] = planner_duration_ms / 1000
|
cycle_detail.time_records["planner"] = planner_duration_ms / 1000
|
||||||
logger.info(
|
# logger.info(
|
||||||
f"{self._runtime.log_prefix} 规划器执行完成: "
|
# f"{self._runtime.log_prefix} 规划器执行完成: "
|
||||||
f"回合={round_index + 1} "
|
# f"回合={round_index + 1} "
|
||||||
f"耗时={cycle_detail.time_records['planner']:.3f} 秒"
|
# f"耗时={cycle_detail.time_records['planner']:.3f} 秒"
|
||||||
)
|
# )
|
||||||
reasoning_content = response.content or ""
|
reasoning_content = response.content or ""
|
||||||
if self._should_replace_reasoning(reasoning_content):
|
if self._should_replace_reasoning(reasoning_content):
|
||||||
response.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后直接输出我的想法"
|
response.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后直接输出我的想法"
|
||||||
@@ -865,7 +865,7 @@ class MaisakaReasoningEngine:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
similarity = self._calculate_similarity(current_content, self._last_reasoning_content)
|
similarity = self._calculate_similarity(current_content, self._last_reasoning_content)
|
||||||
logger.info(f"{self._runtime.log_prefix} 思考内容相似度: {similarity:.2f}")
|
logger.debug(f"{self._runtime.log_prefix} 思考内容相似度: {similarity:.2f}")
|
||||||
return similarity > 0.9
|
return similarity > 0.9
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user