优化对上下文的压缩,新增表达方式快速版本
This commit is contained in:
@@ -292,8 +292,15 @@ class MaisakaChatLoopService:
|
||||
"file_tools_section": tools_section,
|
||||
"group_chat_attention_block": self._build_group_chat_attention_block(),
|
||||
"identity": self._personality_prompt,
|
||||
"time_block": self._build_time_block(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _build_time_block() -> str:
|
||||
"""构建当前时间提示块。"""
|
||||
|
||||
return f"当前时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
|
||||
def _build_group_chat_attention_block(self) -> str:
|
||||
"""构建当前聊天场景下的额外注意事项块。"""
|
||||
|
||||
|
||||
125
src/maisaka/history_post_processor.py
Normal file
125
src/maisaka/history_post_processor.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""Maisaka 历史消息轮次结束后处理。"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .context_messages import AssistantMessage, LLMContextMessage, ToolResultMessage
|
||||
from .history_utils import drop_leading_orphan_tool_results, drop_orphan_tool_results
|
||||
|
||||
TIMING_HISTORY_TOOL_NAMES = {"continue", "finish", "no_reply", "wait"}
|
||||
EARLY_TRIM_RATIO = 0.2
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class HistoryPostProcessResult:
|
||||
"""历史后处理结果。"""
|
||||
|
||||
history: list[LLMContextMessage]
|
||||
removed_count: int
|
||||
remaining_context_count: int
|
||||
|
||||
|
||||
def process_chat_history_after_cycle(
|
||||
chat_history: list[LLMContextMessage],
|
||||
*,
|
||||
max_context_size: int,
|
||||
) -> HistoryPostProcessResult:
|
||||
"""在每轮结束后统一执行历史裁切与清理。"""
|
||||
|
||||
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)
|
||||
|
||||
processed_history, orphan_removed_count = drop_orphan_tool_results(processed_history)
|
||||
remaining_context_count = sum(1 for message in processed_history if message.count_in_context)
|
||||
removed_overflow_count = 0
|
||||
|
||||
while remaining_context_count > max_context_size and processed_history:
|
||||
removed_message = processed_history.pop(0)
|
||||
removed_overflow_count += 1
|
||||
if removed_message.count_in_context:
|
||||
remaining_context_count -= 1
|
||||
|
||||
processed_history, leading_orphan_removed_count = drop_leading_orphan_tool_results(processed_history)
|
||||
removed_overflow_count += leading_orphan_removed_count
|
||||
remaining_context_count = sum(1 for message in processed_history if message.count_in_context)
|
||||
removed_count = (
|
||||
removed_timing_tool_count
|
||||
+ removed_assistant_thought_count
|
||||
+ orphan_removed_count
|
||||
+ removed_overflow_count
|
||||
)
|
||||
return HistoryPostProcessResult(
|
||||
history=processed_history,
|
||||
removed_count=removed_count,
|
||||
remaining_context_count=remaining_context_count,
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
"""移除最早 20% 的非工具 assistant 思考内容。"""
|
||||
|
||||
candidate_indexes = [
|
||||
index
|
||||
for index, message in enumerate(chat_history)
|
||||
if isinstance(message, AssistantMessage)
|
||||
and not message.tool_calls
|
||||
and message.source_kind != "perception"
|
||||
and bool(message.content.strip())
|
||||
]
|
||||
remove_count = int(len(candidate_indexes) * EARLY_TRIM_RATIO)
|
||||
if remove_count <= 0:
|
||||
return 0
|
||||
|
||||
removed_indexes = set(candidate_indexes[:remove_count])
|
||||
filtered_history: list[LLMContextMessage] = []
|
||||
removed_total = 0
|
||||
for index, message in enumerate(chat_history):
|
||||
if index in removed_indexes:
|
||||
removed_total += 1
|
||||
continue
|
||||
filtered_history.append(message)
|
||||
|
||||
chat_history[:] = filtered_history
|
||||
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)
|
||||
@@ -34,7 +34,8 @@ from .context_messages import (
|
||||
ToolResultMessage,
|
||||
contains_complex_message,
|
||||
)
|
||||
from .history_utils import build_prefixed_message_sequence, build_session_message_visible_text, drop_leading_orphan_tool_results
|
||||
from .history_post_processor import process_chat_history_after_cycle
|
||||
from .history_utils import build_prefixed_message_sequence, build_session_message_visible_text
|
||||
from .monitor_events import (
|
||||
emit_cycle_start,
|
||||
emit_message_ingested,
|
||||
@@ -375,8 +376,6 @@ class MaisakaReasoningEngine:
|
||||
self._runtime._chat_history.append(
|
||||
self._build_wait_completed_message(has_new_messages=False)
|
||||
)
|
||||
self._trim_chat_history()
|
||||
|
||||
try:
|
||||
timing_gate_required = True
|
||||
for round_index in range(self._runtime._max_internal_rounds):
|
||||
@@ -472,8 +471,8 @@ class MaisakaReasoningEngine:
|
||||
)
|
||||
reasoning_content = response.content or ""
|
||||
if self._should_replace_reasoning(reasoning_content):
|
||||
response.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后先输出想法再使用工具"
|
||||
response.raw_message.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后先输出想法再使用工具"
|
||||
response.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后直接输出我的想法"
|
||||
response.raw_message.content = "我应该根据我上面思考的内容进行反思,重新思考我下一步的行动,我需要分析当前场景,对话,以及我可以使用的工具,然后直接输出我的想法"
|
||||
logger.info(f"{self._runtime.log_prefix} 当前思考与上一轮过于相似,已替换为重新思考提示")
|
||||
|
||||
self._last_reasoning_content = reasoning_content
|
||||
@@ -502,10 +501,7 @@ class MaisakaReasoningEngine:
|
||||
)
|
||||
interrupted_at = time.time()
|
||||
interrupted_stage_label = "Planner"
|
||||
interrupted_text = (
|
||||
"Planner 在流式响应阶段被新消息打断。"
|
||||
"本轮未完成,因此这里展示的是中断说明而不是完整返回。"
|
||||
)
|
||||
interrupted_text = "Planner 收到新消息,开始重新决策"
|
||||
interrupted_response = ChatResponse(
|
||||
content=interrupted_text or None,
|
||||
tool_calls=[],
|
||||
@@ -528,9 +524,7 @@ class MaisakaReasoningEngine:
|
||||
"状态:已被新消息打断",
|
||||
f"打断位置:{interrupted_stage_label} 请求流式响应阶段",
|
||||
f"打断耗时:{interrupted_at - current_stage_started_at:.3f} 秒",
|
||||
f"打断原因:{str(exc) or '收到外部中断信号'}",
|
||||
]
|
||||
interrupted_extra_lines.append("展示内容:以下为 Maisaka 侧记录的中断说明")
|
||||
response = interrupted_response
|
||||
planner_extra_lines = interrupted_extra_lines
|
||||
logger.info(
|
||||
@@ -695,7 +689,6 @@ class MaisakaReasoningEngine:
|
||||
continue
|
||||
|
||||
self._insert_chat_history_message(history_message)
|
||||
self._trim_chat_history()
|
||||
|
||||
# 向监控前端广播新消息注入事件
|
||||
user_info = message.message_info.user_info
|
||||
@@ -798,6 +791,7 @@ class MaisakaReasoningEngine:
|
||||
"""结束并记录一轮 Maisaka 思考循环。"""
|
||||
cycle_detail.end_time = time.time()
|
||||
self._runtime.history_loop.append(cycle_detail)
|
||||
self._post_process_chat_history_after_cycle()
|
||||
|
||||
timer_strings = [
|
||||
f"{name}: {duration:.2f}s"
|
||||
@@ -807,26 +801,20 @@ class MaisakaReasoningEngine:
|
||||
self._runtime._log_cycle_completed(cycle_detail, timer_strings)
|
||||
return cycle_detail
|
||||
|
||||
def _trim_chat_history(self) -> None:
|
||||
def _post_process_chat_history_after_cycle(self) -> None:
|
||||
"""裁剪聊天历史,保证用户消息数量不超过配置限制。"""
|
||||
conversation_message_count = sum(1 for message in self._runtime._chat_history if message.count_in_context)
|
||||
if conversation_message_count <= self._runtime._max_context_size:
|
||||
process_result = process_chat_history_after_cycle(
|
||||
self._runtime._chat_history,
|
||||
max_context_size=self._runtime._max_context_size,
|
||||
)
|
||||
if process_result.removed_count <= 0:
|
||||
return
|
||||
|
||||
trimmed_history = list(self._runtime._chat_history)
|
||||
removed_count = 0
|
||||
|
||||
while conversation_message_count > self._runtime._max_context_size and trimmed_history:
|
||||
removed_message = trimmed_history.pop(0)
|
||||
removed_count += 1
|
||||
if removed_message.count_in_context:
|
||||
conversation_message_count -= 1
|
||||
|
||||
trimmed_history, pruned_orphan_count = drop_leading_orphan_tool_results(trimmed_history)
|
||||
removed_count += pruned_orphan_count
|
||||
|
||||
self._runtime._chat_history = trimmed_history
|
||||
self._runtime._log_history_trimmed(removed_count, conversation_message_count)
|
||||
self._runtime._chat_history = process_result.history
|
||||
self._runtime._log_history_trimmed(
|
||||
process_result.removed_count,
|
||||
process_result.remaining_context_count,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _calculate_similarity(text1: str, text2: str) -> float:
|
||||
|
||||
@@ -437,6 +437,7 @@ class MaisakaHeartFlowChatting:
|
||||
|
||||
selected_history, _ = MaisakaChatLoopService.select_llm_context_messages(
|
||||
self._chat_history,
|
||||
request_kind=request_kind,
|
||||
max_context_size=context_message_limit,
|
||||
)
|
||||
sub_agent_history = list(selected_history)
|
||||
@@ -748,7 +749,7 @@ class MaisakaHeartFlowChatting:
|
||||
return True
|
||||
|
||||
async def _trigger_expression_learning(self, messages: list[SessionMessage]) -> None:
|
||||
"""?????????????????"""
|
||||
"""触发表达方式学习"""
|
||||
pending_count = self._expression_learner.get_pending_count(self.message_cache)
|
||||
if not self._should_trigger_learning(
|
||||
enabled=self._enable_expression_learning,
|
||||
@@ -761,21 +762,21 @@ class MaisakaHeartFlowChatting:
|
||||
|
||||
self._last_expression_extraction_time = time.time()
|
||||
logger.info(
|
||||
f"{self.log_prefix} ??????: "
|
||||
f"??????={len(messages)} ??????={pending_count} "
|
||||
f"?????={len(self.message_cache)} "
|
||||
f"??????={self._enable_jargon_learning}"
|
||||
f"{self.log_prefix} 触发表达方式学习: "
|
||||
f"消息数量={len(messages)} 待处理消息数量={pending_count} "
|
||||
f"缓存总量={len(self.message_cache)} "
|
||||
f"是否启用黑话学习={self._enable_jargon_learning}"
|
||||
)
|
||||
|
||||
try:
|
||||
jargon_miner = self._jargon_miner if self._enable_jargon_learning else None
|
||||
learnt_style = await self._expression_learner.learn(self.message_cache, jargon_miner)
|
||||
if learnt_style:
|
||||
logger.info(f"{self.log_prefix} ???????")
|
||||
logger.info(f"{self.log_prefix} 表达方式学习成功")
|
||||
else:
|
||||
logger.debug(f"{self.log_prefix} ???????????????")
|
||||
logger.debug(f"{self.log_prefix} 表达方式学习失败")
|
||||
except Exception:
|
||||
logger.exception(f"{self.log_prefix} ??????")
|
||||
logger.exception(f"{self.log_prefix} 表达方式学习异常")
|
||||
|
||||
async def _init_mcp(self) -> None:
|
||||
"""初始化 MCP 工具并注册到统一工具层。"""
|
||||
@@ -787,12 +788,12 @@ class MaisakaHeartFlowChatting:
|
||||
host_callbacks=self._mcp_host_bridge.build_callbacks(),
|
||||
)
|
||||
if self._mcp_manager is None:
|
||||
logger.info(f"{self.log_prefix} MCP 管理器不可用")
|
||||
logger.info(f"{self.log_prefix} Maisaka MCP 管理器不可用")
|
||||
return
|
||||
|
||||
mcp_tool_specs = self._mcp_manager.get_tool_specs()
|
||||
if not mcp_tool_specs:
|
||||
logger.info(f"{self.log_prefix} 没有可供 Maisaka 使用的 MCP 工具")
|
||||
logger.info(f"{self.log_prefix} Maisaka 没有可供使用的 MCP 工具")
|
||||
return
|
||||
|
||||
self._tool_registry.register_provider(MCPToolProvider(self._mcp_manager))
|
||||
|
||||
Reference in New Issue
Block a user