From 5cdca2bbd45bd8a29d0601e3ee9d415537960be8 Mon Sep 17 00:00:00 2001 From: SengokuCola <1026294844@qq.com> Date: Fri, 3 Apr 2026 18:33:51 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E9=99=8D=E4=BD=8Equote=E7=9A=84?= =?UTF-8?q?=E6=A6=82=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/maisaka/builtin_tool/reply.py | 29 +++++++----------- src/maisaka/chat_loop_service.py | 13 -------- src/maisaka/reasoning_engine.py | 46 ++++++++++++++++++++++------- src/maisaka/runtime.py | 49 ++++++++++++++++++++++++------- 4 files changed, 84 insertions(+), 53 deletions(-) diff --git a/src/maisaka/builtin_tool/reply.py b/src/maisaka/builtin_tool/reply.py index ac7fe767..dc6ef24c 100644 --- a/src/maisaka/builtin_tool/reply.py +++ b/src/maisaka/builtin_tool/reply.py @@ -22,8 +22,7 @@ def get_tool_spec() -> ToolSpec: detailed_description=( "参数说明:\n" "- msg_id:string,必填。要回复的目标用户消息编号。\n" - "- quote:boolean,可选。当有非常明确的回复目标时,以引用回复的方式发送,默认 true。\n" - "- unknown_words:array,可选。回复前可能需要查询的黑话或词条列表。" + "- set_quote:boolean,可选。以引用回复的方式发送,默认 true。" ), parameters_schema={ "type": "object", @@ -32,16 +31,11 @@ def get_tool_spec() -> ToolSpec: "type": "string", "description": "要回复的目标用户消息编号。", }, - "quote": { + "set_quote": { "type": "boolean", - "description": "当有非常明确的回复目标时,以引用回复的方式发送。", + "description": "以引用回复的方式发送这条回复,不用每句都引用。", "default": True, }, - "unknown_words": { - "type": "array", - "description": "回复前可能需要查询的黑话或词条列表。", - "items": {"type": "string"}, - }, }, "required": ["msg_id"], }, @@ -59,9 +53,7 @@ async def handle_tool( latest_thought = context.reasoning if context is not None else invocation.reasoning target_message_id = str(invocation.arguments.get("msg_id") or "").strip() - quote_reply = bool(invocation.arguments.get("quote", True)) - raw_unknown_words = invocation.arguments.get("unknown_words") - unknown_words = raw_unknown_words if isinstance(raw_unknown_words, list) else None + set_quote = bool(invocation.arguments.get("set_quote", True)) if not target_message_id: return tool_ctx.build_failure_result( @@ -77,8 +69,8 @@ async def handle_tool( ) logger.info( - f"{tool_ctx.runtime.log_prefix} 已触发回复工具 " - f"目标消息编号={target_message_id} 引用回复={quote_reply} 最新思考={latest_thought!r}" + f"{tool_ctx.runtime.log_prefix} 已触发回复工具," + f"目标消息编号={target_message_id} 引用回复={set_quote} 最新思考={latest_thought!r}" ) try: replyer = replyer_manager.get_replyer( @@ -108,7 +100,6 @@ async def handle_tool( stream_id=tool_ctx.runtime.session_id, reply_message=target_message, chat_history=tool_ctx.runtime._chat_history, - unknown_words=unknown_words, log_reply=False, ) except Exception as exc: @@ -144,8 +135,8 @@ async def handle_tool( sent = await send_service.text_to_stream( text=segment, stream_id=tool_ctx.runtime.session_id, - set_reply=quote_reply if index == 0 else False, - reply_message=target_message if quote_reply and index == 0 else None, + set_reply=set_quote if index == 0 else False, + reply_message=target_message if set_quote and index == 0 else None, selected_expressions=reply_result.selected_expression_ids or None, typing=index > 0, ) @@ -166,7 +157,7 @@ async def handle_tool( "可见回复生成成功,但发送失败。", structured_content={ "msg_id": target_message_id, - "quote": quote_reply, + "set_quote": set_quote, "reply_segments": reply_segments, }, ) @@ -180,7 +171,7 @@ async def handle_tool( "回复已生成并发送。", structured_content={ "msg_id": target_message_id, - "quote": quote_reply, + "set_quote": set_quote, "reply_text": combined_reply_text, "reply_segments": reply_segments, "target_user_name": target_user_name, diff --git a/src/maisaka/chat_loop_service.py b/src/maisaka/chat_loop_service.py index 236277b6..93dba8b5 100644 --- a/src/maisaka/chat_loop_service.py +++ b/src/maisaka/chat_loop_service.py @@ -821,19 +821,6 @@ class MaisakaChatLoopService: ) total_tokens = self._coerce_int(after_response_kwargs.get("total_tokens"), generation_result.total_tokens) - tool_call_summaries = [ - { - "调用编号": getattr(tool_call, "call_id", getattr(tool_call, "id", None)), - "工具名": getattr(tool_call, "func_name", getattr(tool_call, "name", None)), - "参数": getattr(tool_call, "args", getattr(tool_call, "arguments", None)), - } - for tool_call in final_tool_calls - ] - logger.info( - f"Maisaka 规划器返回结果: 内容={final_response!r} " - f"工具调用={tool_call_summaries}" - ) - raw_message = AssistantMessage( content=final_response, timestamp=datetime.now(), diff --git a/src/maisaka/reasoning_engine.py b/src/maisaka/reasoning_engine.py index 72539a5b..2b20c1d3 100644 --- a/src/maisaka/reasoning_engine.py +++ b/src/maisaka/reasoning_engine.py @@ -125,24 +125,33 @@ class MaisakaReasoningEngine: logger.info(f"{self._runtime.log_prefix} 当前思考与上一轮过于相似,已替换为重新思考提示") self._last_reasoning_content = reasoning_content - self._runtime._render_context_usage_panel( - selected_history_count=response.selected_history_count, - prompt_tokens=response.prompt_tokens, - ) self._runtime._chat_history.append(response.raw_message) + tool_result_summaries: list[str] = [] if response.tool_calls: tool_started_at = time.time() - should_pause = await self._handle_tool_calls( + should_pause, tool_result_summaries = await self._handle_tool_calls( response.tool_calls, response.content or "", anchor_message, ) cycle_detail.time_records["tool_calls"] = time.time() - tool_started_at + self._runtime._render_context_usage_panel( + selected_history_count=response.selected_history_count, + prompt_tokens=response.prompt_tokens, + planner_response=response.content or "", + tool_calls=response.tool_calls, + tool_results=tool_result_summaries, + ) if should_pause: break continue + self._runtime._render_context_usage_panel( + selected_history_count=response.selected_history_count, + prompt_tokens=response.prompt_tokens, + planner_response=response.content or "", + ) if response.content: continue @@ -701,12 +710,25 @@ class MaisakaReasoningEngine: ) ) + def _build_tool_result_summary(self, tool_call: ToolCall, result: ToolExecutionResult) -> str: + """构建用于终端展示的工具结果摘要。""" + + history_content = result.get_history_content().strip() + if not history_content: + history_content = result.error_message.strip() + if not history_content: + history_content = "执行成功" if result.success else "执行失败" + + summary_prefix = "[成功]" if result.success else "[失败]" + normalized_content = self._truncate_tool_record_text(history_content, max_length=200) + return f"- {tool_call.func_name} {summary_prefix}: {normalized_content}" + async def _handle_tool_calls( self, tool_calls: list[ToolCall], latest_thought: str, anchor_message: SessionMessage, - ) -> bool: + ) -> tuple[bool, list[str]]: """执行一批统一工具调用。 Args: @@ -715,9 +737,11 @@ class MaisakaReasoningEngine: anchor_message: 当前轮的锚点消息。 Returns: - bool: 是否需要暂停当前思考循环。 + tuple[bool, list[str]]: 是否需要暂停当前思考循环,以及工具结果摘要列表。 """ + tool_result_summaries: list[str] = [] + if self._runtime._tool_registry is None: for tool_call in tool_calls: invocation = self._build_tool_invocation(tool_call, latest_thought) @@ -728,7 +752,8 @@ class MaisakaReasoningEngine: ) await self._store_tool_execution_record(invocation, result, None) self._append_tool_execution_result(tool_call, result) - return False + tool_result_summaries.append(self._build_tool_result_summary(tool_call, result)) + return False, tool_result_summaries execution_context = self._build_tool_execution_context(latest_thought, anchor_message) tool_spec_map = { @@ -744,12 +769,13 @@ class MaisakaReasoningEngine: tool_spec_map.get(invocation.tool_name), ) self._append_tool_execution_result(tool_call, result) + tool_result_summaries.append(self._build_tool_result_summary(tool_call, result)) if not result.success and tool_call.func_name == "reply": logger.warning(f"{self._runtime.log_prefix} 回复工具未生成可见消息,将继续下一轮循环") if bool(result.metadata.get("pause_execution", False)): - return True + return True, tool_result_summaries - return False + return False, tool_result_summaries diff --git a/src/maisaka/runtime.py b/src/maisaka/runtime.py index 33efb4dd..1617dbd2 100644 --- a/src/maisaka/runtime.py +++ b/src/maisaka/runtime.py @@ -1,6 +1,6 @@ """Maisaka 非 CLI 运行时。""" -from typing import Literal, Optional +from typing import Any, Literal, Optional import asyncio import time @@ -450,28 +450,55 @@ class MaisakaHeartFlowChatting: *, selected_history_count: int, prompt_tokens: int, + planner_response: str = "", + tool_calls: Optional[list[Any]] = None, + tool_results: Optional[list[str]] = None, ) -> None: - """在终端展示当前聊天流的上下文占用情况。""" + """在终端展示当前聊天流的上下文占用、规划结果与工具摘要。""" if not global_config.maisaka.show_thinking: return session_name = chat_manager.get_session_name(self.session_id) or self.session_id - body = "\n".join( - [ - f"聊天流: {session_name}", - f"Chat ID: {self.session_id}", - f"上下文占用: {selected_history_count}条 / {self._format_token_count(prompt_tokens)}", - ] - ) + body_lines = [ + f"聊天流: {session_name}", + f"Chat ID: {self.session_id}", + f"上下文占用: {selected_history_count}条 / {self._format_token_count(prompt_tokens)}", + ] + + normalized_response = planner_response.strip() + if normalized_response: + body_lines.extend(["", "Maisaka 返回:", normalized_response]) + + normalized_tool_calls = self._build_tool_call_summary_lines(tool_calls or []) + if normalized_tool_calls: + body_lines.extend(["", "工具调用:", *normalized_tool_calls]) + + normalized_tool_results = [result.strip() for result in tool_results or [] if isinstance(result, str) and result.strip()] + if normalized_tool_results: + body_lines.extend(["", "工具结果:", *normalized_tool_results]) + console.print( Panel( - Text(body), - title="MaiSaka 上下文占用", + Text("\n".join(body_lines)), + title="MaiSaka 上下文与结果", border_style="bright_blue", padding=(0, 1), ) ) + @staticmethod + def _build_tool_call_summary_lines(tool_calls: list[Any]) -> list[str]: + """构建工具调用摘要文本。""" + summary_lines: list[str] = [] + for tool_call in tool_calls: + tool_name = str(getattr(tool_call, "func_name", getattr(tool_call, "name", "")) or "").strip() or "unknown" + tool_args = getattr(tool_call, "args", getattr(tool_call, "arguments", None)) + if isinstance(tool_args, dict) and tool_args: + summary_lines.append(f"- {tool_name}: {tool_args}") + else: + summary_lines.append(f"- {tool_name}") + return summary_lines + def _log_cycle_started(self, cycle_detail: CycleDetail, round_index: int) -> None: logger.info( f"{self.log_prefix} MaiSaka 轮次开始: 循环编号={cycle_detail.cycle_id} "