feat:修复孤儿工具报错,为replyer等tool添加统一的控制台展示接口

This commit is contained in:
SengokuCola
2026-04-07 16:21:42 +08:00
parent c5f514946b
commit 6968879a04
11 changed files with 1803 additions and 439 deletions

View File

@@ -11,6 +11,7 @@ from src.common.data_models.reply_generation_data_models import (
GenerationMetrics,
LLMCompletionResult,
ReplyGenerationResult,
build_reply_monitor_detail,
)
from src.common.logger import get_logger
from src.common.prompt_i18n import load_prompt
@@ -18,10 +19,17 @@ from src.config.config import global_config
from src.core.types import ActionInfo
from src.services.llm_service import LLMServiceClient
from src.maisaka.context_messages import AssistantMessage, LLMContextMessage, ReferenceMessage, SessionBackedMessage, ToolResultMessage
from .maisaka_expression_selector import maisaka_expression_selector
from src.maisaka.context_messages import (
AssistantMessage,
LLMContextMessage,
ReferenceMessage,
SessionBackedMessage,
ToolResultMessage,
)
from src.maisaka.message_adapter import parse_speaker_content
from .maisaka_expression_selector import maisaka_expression_selector
logger = get_logger("replyer")
@@ -50,7 +58,7 @@ class MaisakaReplyGenerator:
self._personality_prompt = self._build_personality_prompt()
def _build_personality_prompt(self) -> str:
"""构建 replyer 使用的人设描述"""
"""构建 replyer 使用的人设提示"""
try:
bot_name = global_config.bot.nickname
alias_names = global_config.bot.alias_names
@@ -268,6 +276,11 @@ class MaisakaReplyGenerator:
sub_agent_runner: Optional[Callable[[str], Awaitable[str]]] = None,
) -> Tuple[bool, ReplyGenerationResult]:
"""结合上下文生成 Maisaka 的最终可见回复。"""
def finalize(success_value: bool) -> Tuple[bool, ReplyGenerationResult]:
result.monitor_detail = build_reply_monitor_detail(result)
return success_value, result
del available_actions
del chosen_actions
del extra_info
@@ -278,14 +291,14 @@ class MaisakaReplyGenerator:
del unknown_words
result = ReplyGenerationResult()
overall_started_at = time.perf_counter()
if chat_history is None:
result.error_message = "聊天历史为空"
return False, result
return finalize(False)
logger.info(
f"Maisaka 回复器开始生成: 会话流标识={stream_id} 回复原因={reply_reason!r} "
f"历史消息数={len(chat_history)} 目标消息编号="
f"{reply_message.message_id if reply_message else None}"
f"历史消息数={len(chat_history)} 目标消息编号={reply_message.message_id if reply_message else None}"
)
filtered_history = [
@@ -293,14 +306,12 @@ class MaisakaReplyGenerator:
for message in chat_history
if not isinstance(message, (ReferenceMessage, ToolResultMessage))
]
logger.debug(f"Maisaka 回复器过滤后历史消息数={len(filtered_history)}")
# Validate that express_model is properly initialized
if self.express_model is None:
logger.error("Maisaka 回复器的回复模型未初始化")
result.error_message = "回复模型尚未初始化"
return False, result
return finalize(False)
try:
reply_context = await self._build_reply_context(
@@ -312,9 +323,13 @@ class MaisakaReplyGenerator:
)
except Exception as exc:
import traceback
logger.error(f"Maisaka 回复器构建回复上下文失败: {exc}\n{traceback.format_exc()}")
result.error_message = f"构建回复上下文失败: {exc}"
return False, result
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
)
return finalize(False)
merged_expression_habits = expression_habits.strip() or reply_context.expression_habits
result.selected_expression_ids = (
@@ -328,6 +343,7 @@ class MaisakaReplyGenerator:
f"已选表达编号={result.selected_expression_ids!r}"
)
prompt_started_at = time.perf_counter()
try:
prompt = self._build_prompt(
chat_history=filtered_history,
@@ -337,26 +353,36 @@ class MaisakaReplyGenerator:
)
except Exception as exc:
import traceback
logger.error(f"Maisaka 回复器构建提示词失败: {exc}\n{traceback.format_exc()}")
result.error_message = f"构建提示词失败: {exc}"
return False, result
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
)
return finalize(False)
prompt_ms = round((time.perf_counter() - prompt_started_at) * 1000, 2)
result.completion.request_prompt = prompt
show_replyer_prompt = bool(getattr(global_config.debug, "show_replyer_prompt", False))
show_replyer_reasoning = bool(getattr(global_config.debug, "show_replyer_reasoning", False))
if global_config.debug.show_replyer_prompt:
logger.info(f"\nMaisaka 回复器提示词\n{prompt}\n")
if show_replyer_prompt:
logger.info(f"\nMaisaka 回复器提示词:\n{prompt}\n")
started_at = time.perf_counter()
llm_started_at = time.perf_counter()
try:
generation_result = await self.express_model.generate_response(prompt)
except Exception as exc:
logger.exception("Maisaka 回复器调用失败")
result.error_message = str(exc)
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - started_at) * 1000, 2),
prompt_ms=prompt_ms,
llm_ms=round((time.perf_counter() - llm_started_at) * 1000, 2),
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
)
return False, result
return finalize(False)
llm_ms = round((time.perf_counter() - llm_started_at) * 1000, 2)
response_text = (generation_result.response or "").strip()
result.success = bool(response_text)
result.completion = LLMCompletionResult(
@@ -365,18 +391,27 @@ class MaisakaReplyGenerator:
reasoning_text=generation_result.reasoning or "",
model_name=generation_result.model_name or "",
tool_calls=generation_result.tool_calls or [],
prompt_tokens=generation_result.prompt_tokens,
completion_tokens=generation_result.completion_tokens,
total_tokens=generation_result.total_tokens,
)
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - started_at) * 1000, 2),
prompt_ms=prompt_ms,
llm_ms=llm_ms,
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
stage_logs=[
f"prompt: {prompt_ms} ms",
f"llm: {llm_ms} ms",
],
)
if global_config.debug.show_replyer_reasoning and result.completion.reasoning_text:
logger.info(f"Maisaka 回复器思考内容\n{result.completion.reasoning_text}")
if show_replyer_reasoning and result.completion.reasoning_text:
logger.info(f"Maisaka 回复器思考内容:\n{result.completion.reasoning_text}")
if not result.success:
result.error_message = "回复器返回了空内容"
logger.warning("Maisaka 回复器返回了空内容")
return False, result
return finalize(False)
logger.info(
f"Maisaka 回复器生成成功: 回复文本={response_text!r} "
@@ -384,4 +419,4 @@ class MaisakaReplyGenerator:
f"已选表达编号={result.selected_expression_ids!r}"
)
result.text_fragments = [response_text]
return True, result
return finalize(True)

View File

@@ -16,13 +16,19 @@ from src.common.data_models.reply_generation_data_models import (
GenerationMetrics,
LLMCompletionResult,
ReplyGenerationResult,
build_reply_monitor_detail,
)
from src.common.logger import get_logger
from src.common.prompt_i18n import load_prompt
from src.config.config import global_config
from src.core.types import ActionInfo
from src.llm_models.payload_content.message import ImageMessagePart, Message, MessageBuilder, RoleType, TextMessagePart
from src.maisaka.monitor_events import emit_replier_request, emit_replier_response
from src.llm_models.payload_content.message import (
ImageMessagePart,
Message,
MessageBuilder,
RoleType,
TextMessagePart,
)
from src.services.llm_service import LLMServiceClient
from src.maisaka.context_messages import (
@@ -32,10 +38,11 @@ from src.maisaka.context_messages import (
SessionBackedMessage,
ToolResultMessage,
)
from .maisaka_expression_selector import maisaka_expression_selector
from src.maisaka.message_adapter import clone_message_sequence, parse_speaker_content
from src.maisaka.prompt_cli_renderer import PromptCLIVisualizer
from .maisaka_expression_selector import maisaka_expression_selector
logger = get_logger("replyer")
@@ -177,7 +184,7 @@ class MaisakaReplyGenerator:
return f"{system_prompt}\n\n" + "\n\n".join(sections)
def _build_reply_instruction(self) -> str:
return "请自然地回复。请注意不要输出多余内容(包括不必要的前后缀冒号括号表情包at或 @等 ),只输出发言内容就好"
return "请自然地回复。不要输出多余说明、括号、at 或额外标记,只输出实际要发送的内容"
def _build_multimodal_user_message(
self,
@@ -342,6 +349,11 @@ class MaisakaReplyGenerator:
selected_expression_ids: Optional[List[int]] = None,
sub_agent_runner: Optional[Callable[[str], Awaitable[str]]] = None,
) -> Tuple[bool, ReplyGenerationResult]:
def finalize(success_value: bool) -> Tuple[bool, ReplyGenerationResult]:
result.monitor_detail = build_reply_monitor_detail(result)
return success_value, result
del available_actions
del chosen_actions
del extra_info
@@ -352,9 +364,10 @@ class MaisakaReplyGenerator:
del unknown_words
result = ReplyGenerationResult()
overall_started_at = time.perf_counter()
if chat_history is None:
result.error_message = "聊天历史为空"
return False, result
return finalize(False)
logger.info(
f"Maisaka 回复器开始生成: 流={stream_id} 原因={reply_reason!r} "
@@ -370,7 +383,7 @@ class MaisakaReplyGenerator:
if self.express_model is None:
logger.error("回复模型未初始化")
result.error_message = "回复模型尚未初始化"
return False, result
return finalize(False)
try:
reply_context = await self._build_reply_context(
@@ -382,9 +395,13 @@ class MaisakaReplyGenerator:
)
except Exception as exc:
import traceback
logger.error(f"构建回复上下文失败: {exc}\n{traceback.format_exc()}")
result.error_message = f"构建回复上下文失败: {exc}"
return False, result
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
)
return finalize(False)
merged_expression_habits = expression_habits.strip() or reply_context.expression_habits
result.selected_expression_ids = (
@@ -397,6 +414,7 @@ class MaisakaReplyGenerator:
f"回复上下文完成: 流={stream_id} 已选表达={result.selected_expression_ids!r}"
)
prompt_started_at = time.perf_counter()
try:
request_messages = self._build_request_messages(
chat_history=filtered_history,
@@ -406,11 +424,18 @@ class MaisakaReplyGenerator:
)
except Exception as exc:
import traceback
logger.error(f"构建提示词失败: {exc}\n{traceback.format_exc()}")
result.error_message = f"构建提示词失败: {exc}"
return False, result
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
)
return finalize(False)
prompt_ms = round((time.perf_counter() - prompt_started_at) * 1000, 2)
prompt_preview = self._build_request_prompt_preview(request_messages)
show_replyer_prompt = bool(getattr(global_config.debug, "show_replyer_prompt", False))
show_replyer_reasoning = bool(getattr(global_config.debug, "show_replyer_reasoning", False))
def message_factory(_client: object) -> List[Message]:
return request_messages
@@ -418,7 +443,7 @@ class MaisakaReplyGenerator:
result.completion.request_prompt = prompt_preview
preview_chat_id = self._resolve_session_id(stream_id)
replyer_prompt_section: RenderableType | None = None
if global_config.debug.show_replyer_prompt:
if show_replyer_prompt:
replyer_prompt_section = PromptCLIVisualizer.build_text_section(
prompt_preview,
category="replyer",
@@ -428,15 +453,7 @@ class MaisakaReplyGenerator:
folded=global_config.debug.fold_maisaka_thinking,
)
started_at = time.perf_counter()
# 向监控前端广播回复器请求事件
await emit_replier_request(
session_id=preview_chat_id,
messages=request_messages,
model_name=getattr(self.express_model, "model_name", ""),
)
llm_started_at = time.perf_counter()
try:
generation_result = await self.express_model.generate_response_with_messages(
message_factory=message_factory
@@ -445,10 +462,13 @@ class MaisakaReplyGenerator:
logger.exception("Maisaka 回复器调用失败")
result.error_message = str(exc)
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - started_at) * 1000, 2),
prompt_ms=prompt_ms,
llm_ms=round((time.perf_counter() - llm_started_at) * 1000, 2),
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
)
return False, result
return finalize(False)
llm_ms = round((time.perf_counter() - llm_started_at) * 1000, 2)
response_text = (generation_result.response or "").strip()
result.success = bool(response_text)
result.completion = LLMCompletionResult(
@@ -457,36 +477,33 @@ class MaisakaReplyGenerator:
reasoning_text=generation_result.reasoning or "",
model_name=generation_result.model_name or "",
tool_calls=generation_result.tool_calls or [],
)
result.metrics = GenerationMetrics(
overall_ms=round((time.perf_counter() - started_at) * 1000, 2),
)
# 向监控前端广播回复器响应事件
await emit_replier_response(
session_id=preview_chat_id,
content=response_text,
reasoning=generation_result.reasoning or "",
model_name=generation_result.model_name or "",
prompt_tokens=generation_result.prompt_tokens,
completion_tokens=generation_result.completion_tokens,
total_tokens=generation_result.total_tokens,
duration_ms=result.metrics.overall_ms or 0.0,
success=result.success,
)
result.metrics = GenerationMetrics(
prompt_ms=prompt_ms,
llm_ms=llm_ms,
overall_ms=round((time.perf_counter() - overall_started_at) * 1000, 2),
stage_logs=[
f"prompt: {prompt_ms} ms",
f"llm: {llm_ms} ms",
],
)
if global_config.debug.show_replyer_reasoning and result.completion.reasoning_text:
logger.info(f"Maisaka 回复器思考内容\n{result.completion.reasoning_text}")
if show_replyer_reasoning and result.completion.reasoning_text:
logger.info(f"Maisaka 回复器思考内容:\n{result.completion.reasoning_text}")
if not result.success:
result.error_message = "回复器返回了空内容"
logger.warning("Maisaka 回复器返回了空内容")
return False, result
return finalize(False)
logger.info(
f"Maisaka 回复器生成成功: 文本={response_text!r} 总耗时ms={result.metrics.overall_ms} 已选表达={result.selected_expression_ids!r}"
f"Maisaka 回复器生成成功: 文本={response_text!r} "
f"总耗时ms={result.metrics.overall_ms} 已选表达={result.selected_expression_ids!r}"
)
if global_config.debug.show_replyer_prompt or global_config.debug.show_replyer_reasoning:
if show_replyer_prompt or show_replyer_reasoning:
summary_lines = [
f"流ID: {preview_chat_id or 'unknown'}",
f"耗时: {result.metrics.overall_ms} ms",
@@ -497,7 +514,7 @@ class MaisakaReplyGenerator:
renderables: List[RenderableType] = [Text("\n".join(summary_lines))]
if replyer_prompt_section is not None:
renderables.append(replyer_prompt_section)
if global_config.debug.show_replyer_reasoning and result.completion.reasoning_text:
if show_replyer_reasoning and result.completion.reasoning_text:
renderables.append(
Panel(
Text(result.completion.reasoning_text),
@@ -523,4 +540,4 @@ class MaisakaReplyGenerator:
)
)
result.text_fragments = [response_text]
return True, result
return finalize(True)