feat(chat): 实现发送错别字后自动撤回修正的功能

引入了一个新的聊天交互机制:当机器人发送了包含“错别字”的消息后,会在短暂延迟后自动撤回该消息,并发送正确的版本。此功能旨在模拟更真实的人类打字行为,增加交互的趣味性和拟人化程度。

主要变更:
- **错别字处理流程**:
  - `ResponseHandler`现在会识别出带有错别字的消息,并在发送后创建一个异步任务来处理后续的修正。
  - 新增`handle_typo_correction`方法,该方法会随机延迟2-4秒,然后调用新的`recall_message` API撤回原消息,并重新发送修正后的内容。
- **API扩展**:
  - `send_api`中增加了`recall_message`函数,用于调用适配器执行消息撤回操作。
  - `send_response`的返回值从单个字符串`reply_text`变更为元组`(reply_text, sent_messages)`,以便将已发送的消息信息(包括ID和类型)传递给上层调用者。
- **数据结构调整**:
  - `process_llm_response`的返回类型从`list[str]`调整为`list[dict[str, str]]`,以支持更复杂的响应类型,如包含原文、错别字和修正建议的`typo`类型。
- **代码优化与重构**:
  - 对`ChineseTypoGenerator`进行了大量的代码清理、注释补充和逻辑优化,使其代码更清晰、更易于维护。
  - 修复了多处代码中的类型注解和潜在的空指针问题,提高了代码的健壮性。
This commit is contained in:
minecraft1024a
2025-09-06 15:44:52 +08:00
committed by Windpicker-owo
parent 7f723e9ff6
commit 89fad16e0e
6 changed files with 313 additions and 202 deletions

View File

@@ -52,7 +52,11 @@ class CycleProcessor:
actions,
) -> Tuple[Dict[str, Any], str, Dict[str, float]]:
with Timer("回复发送", cycle_timers):
reply_text = await self.response_handler.send_response(response_set, loop_start_time, action_message)
reply_text, sent_messages = await self.response_handler.send_response(
response_set, loop_start_time, action_message
)
if sent_messages:
asyncio.create_task(self.response_handler.handle_typo_correction(sent_messages))
# 存储reply action信息
person_info_manager = get_person_info_manager()
@@ -148,7 +152,7 @@ class CycleProcessor:
cycle_timers, thinking_id = self.cycle_tracker.start_cycle()
logger.info(f"{self.log_prefix} 开始第{self.context.cycle_counter}次思考")
if ENABLE_S4U:
if ENABLE_S4U and self.context.chat_stream and self.context.chat_stream.user_info:
await send_typing(self.context.chat_stream.user_info.user_id)
loop_start_time = time.time()
@@ -175,7 +179,7 @@ class CycleProcessor:
result = await event_manager.trigger_event(
EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.chat_stream
)
if not result.all_continue_process():
if result and not result.all_continue_process():
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
with Timer("规划器", cycle_timers):
@@ -380,7 +384,7 @@ class CycleProcessor:
if fallback_action and fallback_action != action:
logger.info(f"{self.context.log_prefix} 使用回退动作: {fallback_action}")
action_handler = self.context.action_manager.create_action(
action_name=fallback_action if isinstance(fallback_action, list) else fallback_action,
action_name=str(fallback_action),
action_data=action_data,
reasoning=f"原动作'{action}'不可用,自动回退。{reasoning}",
cycle_timers=cycle_timers,

View File

@@ -1,5 +1,6 @@
import time
import random
import asyncio
from typing import Dict, Any, Tuple
from src.common.logger import get_logger
@@ -59,7 +60,9 @@ class ResponseHandler:
- 构建并返回完整的循环信息
- 用于上级方法的状态跟踪
"""
reply_text = await self.send_response(response_set, loop_start_time, action_message)
reply_text, sent_messages = await self.send_response(response_set, loop_start_time, action_message)
if sent_messages:
asyncio.create_task(self.handle_typo_correction(sent_messages))
person_info_manager = get_person_info_manager()
@@ -100,18 +103,17 @@ class ResponseHandler:
return loop_info, reply_text, cycle_timers
async def send_response(self, reply_set, thinking_start_time, message_data) -> str:
async def send_response(self, reply_set, thinking_start_time, message_data) -> tuple[str, list[dict[str, str]]]:
"""
发送回复内容的具体实现
Args:
reply_set: 回复内容集合,包含多个回复段
reply_to: 回复目标
thinking_start_time: 思考开始时间
message_data: 消息数据
Returns:
str: 完整的回复文本
tuple[str, list[dict[str, str]]]: (完整的回复文本, 已发送消息列表)
功能说明:
- 检查是否有新消息需要回复
@@ -128,19 +130,17 @@ class ResponseHandler:
need_reply = new_message_count >= random.randint(2, 4)
reply_text = ""
sent_messages = []
is_proactive_thinking = message_data.get("message_type") == "proactive_thinking"
first_replied = False
for reply_seg in reply_set:
# 调试日志验证reply_seg的格式
logger.debug(f"Processing reply_seg type: {type(reply_seg)}, content: {reply_seg}")
# 修正:正确处理元组格式 (格式为: (type, content))
if isinstance(reply_seg, tuple) and len(reply_seg) >= 2:
_, data = reply_seg
if reply_seg["type"] == "typo":
data = reply_seg["typo"]
else:
# 向下兼容:如果已经是字符串,则直接使用
data = str(reply_seg)
data = reply_seg["content"]
reply_text += data
@@ -149,7 +149,7 @@ class ResponseHandler:
continue
if not first_replied:
await send_api.text_to_stream(
sent_message = await send_api.text_to_stream(
text=data,
stream_id=self.context.stream_id,
reply_to_message=message_data,
@@ -158,12 +158,32 @@ class ResponseHandler:
)
first_replied = True
else:
await send_api.text_to_stream(
sent_message = await send_api.text_to_stream(
text=data,
stream_id=self.context.stream_id,
reply_to_message=None,
set_reply=False,
typing=True,
)
if sent_message and reply_seg["type"] == "typo":
sent_messages.append(
{
"type": "typo",
"message_id": sent_message,
"original_message": message_data,
"correction": reply_seg["correction"],
}
)
return reply_text
return reply_text, sent_messages
async def handle_typo_correction(self, sent_messages: list[dict[str, Any]]):
"""处理错别字修正"""
for msg in sent_messages:
if msg["type"] == "typo":
await asyncio.sleep(random.uniform(2, 4))
recalled = await send_api.recall_message(str(msg["message_id"]), self.context.stream_id)
if recalled:
await send_api.text_to_stream(
str(msg["correction"]), self.context.stream_id, reply_to_message=msg["original_message"]
)