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

@@ -36,12 +36,10 @@ from .context_messages import (
)
from .history_utils import build_prefixed_message_sequence, build_session_message_visible_text, drop_leading_orphan_tool_results
from .monitor_events import (
emit_cycle_end,
emit_cycle_start,
emit_message_ingested,
emit_planner_response,
emit_planner_finalized,
emit_timing_gate_result,
emit_tool_execution,
)
from .planner_message_utils import build_planner_user_prefix_from_session_message
@@ -279,6 +277,7 @@ class MaisakaReasoningEngine:
ChatResponse(
content=reason,
tool_calls=[],
request_messages=[],
raw_message=AssistantMessage(
content="",
timestamp=datetime.now(),
@@ -288,6 +287,7 @@ class MaisakaReasoningEngine:
sum(1 for message in self._runtime._chat_history if message.count_in_context),
self._runtime._max_context_size,
),
tool_count=0,
prompt_tokens=0,
built_message_count=0,
completion_tokens=0,
@@ -346,6 +346,9 @@ class MaisakaReasoningEngine:
history_count=len(self._runtime._chat_history),
)
planner_started_at = 0.0
planner_duration_ms = 0.0
response: Optional[ChatResponse] = None
tool_monitor_results: list[dict[str, Any]] = []
try:
visual_refresh_started_at = time.time()
refreshed_message_count = await self._refresh_chat_history_visual_placeholders()
@@ -403,17 +406,6 @@ class MaisakaReasoningEngine:
f"回合={round_index + 1} "
f"耗时={cycle_detail.time_records['planner']:.3f}"
)
await emit_planner_response(
session_id=self._runtime.session_id,
cycle_id=cycle_detail.cycle_id,
content=response.content,
tool_calls=response.tool_calls,
prompt_tokens=response.prompt_tokens,
completion_tokens=response.completion_tokens,
total_tokens=response.total_tokens,
duration_ms=planner_duration_ms,
)
reasoning_content = response.content or ""
if self._should_replace_reasoning(reasoning_content):
response.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后先输出想法再使用工具"
@@ -423,10 +415,11 @@ class MaisakaReasoningEngine:
self._last_reasoning_content = reasoning_content
self._runtime._chat_history.append(response.raw_message)
tool_result_summaries: list[str] = []
tool_monitor_results = []
if response.tool_calls:
tool_started_at = time.time()
should_pause, tool_result_summaries = await self._handle_tool_calls(
should_pause, tool_result_summaries, tool_monitor_results = await self._handle_tool_calls(
response.tool_calls,
response.content or "",
anchor_message,
@@ -463,13 +456,24 @@ class MaisakaReasoningEngine:
)
break
finally:
self._end_cycle(cycle_detail)
await emit_cycle_end(
session_id=self._runtime.session_id,
cycle_id=cycle_detail.cycle_id,
time_records=dict(cycle_detail.time_records),
agent_state=self._runtime._agent_state,
)
completed_cycle = self._end_cycle(cycle_detail)
if response is not None:
await emit_planner_finalized(
session_id=self._runtime.session_id,
cycle_id=cycle_detail.cycle_id,
request_messages=response.request_messages,
selected_history_count=response.selected_history_count,
tool_count=response.tool_count,
planner_content=response.content,
planner_tool_calls=response.tool_calls,
prompt_tokens=response.prompt_tokens,
completion_tokens=response.completion_tokens,
total_tokens=response.total_tokens,
duration_ms=planner_duration_ms,
tools=tool_monitor_results,
time_records=dict(completed_cycle.time_records),
agent_state=self._runtime._agent_state,
)
finally:
if self._runtime._agent_state == self._runtime._STATE_RUNNING:
self._runtime._agent_state = self._runtime._STATE_STOP
@@ -683,7 +687,7 @@ class MaisakaReasoningEngine:
def _drop_leading_orphan_tool_results(
chat_history: list[LLMContextMessage],
) -> tuple[list[LLMContextMessage], int]:
"""清理历史前缀中缺少对应 assistant tool_call 的工具结果消息。"""
"""清理历史窗口中缺少对应 assistant tool_call 的工具结果消息。"""
return drop_leading_orphan_tool_results(chat_history)
@@ -1039,12 +1043,38 @@ class MaisakaReasoningEngine:
normalized_content = self._truncate_tool_record_text(history_content, max_length=200)
return f"- {tool_call.func_name} {summary_prefix}: {normalized_content}"
def _build_tool_monitor_result(
self,
tool_call: ToolCall,
invocation: ToolInvocation,
result: ToolExecutionResult,
duration_ms: float,
) -> dict[str, Any]:
"""构建 planner.finalized 中单个工具的监控结果。"""
monitor_detail = result.metadata.get("monitor_detail")
normalized_detail = None
if monitor_detail is not None:
normalized_detail = self._normalize_tool_record_value(monitor_detail)
return {
"tool_call_id": tool_call.call_id,
"tool_name": tool_call.func_name,
"tool_args": self._normalize_tool_record_value(
invocation.arguments if isinstance(invocation.arguments, dict) else {}
),
"success": result.success,
"duration_ms": round(duration_ms, 2),
"summary": self._build_tool_result_summary(tool_call, result),
"detail": normalized_detail,
}
async def _handle_tool_calls(
self,
tool_calls: list[ToolCall],
latest_thought: str,
anchor_message: SessionMessage,
) -> tuple[bool, list[str]]:
) -> tuple[bool, list[str], list[dict[str, Any]]]:
"""执行一批统一工具调用。
Args:
@@ -1057,6 +1087,7 @@ class MaisakaReasoningEngine:
"""
tool_result_summaries: list[str] = []
tool_monitor_results: list[dict[str, Any]] = []
if self._runtime._tool_registry is None:
for tool_call in tool_calls:
@@ -1069,7 +1100,10 @@ class MaisakaReasoningEngine:
await self._store_tool_execution_record(invocation, result, None)
self._append_tool_execution_result(tool_call, result)
tool_result_summaries.append(self._build_tool_result_summary(tool_call, result))
return False, tool_result_summaries
tool_monitor_results.append(
self._build_tool_monitor_result(tool_call, invocation, result, duration_ms=0.0)
)
return False, tool_result_summaries, tool_monitor_results
execution_context = self._build_tool_execution_context(latest_thought, anchor_message)
tool_spec_map = {
@@ -1088,24 +1122,17 @@ class MaisakaReasoningEngine:
)
self._append_tool_execution_result(tool_call, result)
tool_result_summaries.append(self._build_tool_result_summary(tool_call, result))
tool_monitor_results.append(
self._build_tool_monitor_result(tool_call, invocation, result, tool_duration_ms)
)
# 向监控前端广播工具执行结果
cycle_id = self._runtime._current_cycle_detail.cycle_id if self._runtime._current_cycle_detail else 0
await emit_tool_execution(
session_id=self._runtime.session_id,
cycle_id=cycle_id,
tool_name=tool_call.func_name,
tool_args=invocation.arguments if isinstance(invocation.arguments, dict) else {},
result_summary=result.content[:500] if result.content else (result.error_message or "")[:500],
success=result.success,
duration_ms=tool_duration_ms,
)
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, tool_result_summaries
return True, tool_result_summaries, tool_monitor_results
return False, tool_result_summaries
return False, tool_result_summaries, tool_monitor_results