allow multi-part replies for xiaoniu qa responses

This commit is contained in:
liuwei
2026-04-07 14:39:41 +08:00
parent 42feb29a40
commit 5a02121ace

View File

@@ -298,13 +298,15 @@ class AIAutoResponsePlugin(MessagePluginInterface):
)
return False, "empty_response"
response = self._finalize_reply(response, reply_mode)
reply_chunks = self._finalize_reply(response, reply_mode)
await bot.send_text_message(room_id, response, sender)
for chunk in reply_chunks:
await bot.send_text_message(room_id, chunk, sender)
self.last_reply_at[room_id] = time.time()
self.flow_manager.note_bot_reply(room_id)
self.memory_store.note_bot_reply(room_id, sender, trigger.topic)
self._upsert_interaction_memory(room_id, sender, sender_name, content, response, trigger.trigger_type, trigger.topic)
final_response_text = "\n".join(reply_chunks)
self._upsert_interaction_memory(room_id, sender, sender_name, content, final_response_text, trigger.trigger_type, trigger.topic)
self._log_event(
"sent",
room_id=room_id,
@@ -312,8 +314,9 @@ class AIAutoResponsePlugin(MessagePluginInterface):
sender_name=sender_name,
trigger_type=trigger.trigger_type,
reply_mode=reply_mode,
response_preview=self._preview(response),
response_len=len(response),
response_preview=self._preview(final_response_text),
response_len=len(final_response_text),
chunk_count=len(reply_chunks),
)
return False, "replied"
@@ -487,31 +490,29 @@ class AIAutoResponsePlugin(MessagePluginInterface):
response = re.sub(r"\n{3,}", "\n\n", response)
return response[:500].strip()
def _finalize_reply(self, response: str, reply_mode: str) -> str:
def _finalize_reply(self, response: str, reply_mode: str) -> List[str]:
text = (response or "").strip()
if not text:
return ""
return []
text = re.sub(r"\s+", " ", text)
text = text.replace("\n", " ").strip()
if reply_mode == "social_short":
text = self._take_first_sentence(text, 12)
return [self._take_first_sentence(text, 12).strip()]
elif reply_mode == "qa_fast":
text = self._take_first_sentence(text, 28)
return self._split_reply_chunks(text, sentence_limit=2, char_limit=28, chunk_limit=2)
elif reply_mode == "qa_with_context":
text = self._take_first_sentence(text, 36)
else:
text = self._take_first_sentence(text, 24)
return text.strip()
return self._split_reply_chunks(text, sentence_limit=2, char_limit=36, chunk_limit=2)
return [self._take_first_sentence(text, 24).strip()]
@staticmethod
def _build_length_rule(reply_mode: str) -> str:
if reply_mode == "social_short":
return "默认只回一句短话最好控制在2到8个字除非非常不自然。"
if reply_mode == "qa_fast":
return "尽量只回1句话总长度优先控制在28字内先给结论不要主动补解释。"
return "优先1句话如果确实需要可以拆成2条很短的话发出,总长度每条优先控制在28字内先给结论不要主动补解释。"
if reply_mode == "qa_with_context":
return "优先控制在1句话必要时最多2句总长度优先控制在36字内只给第一层答案。"
return "优先控制在1句话必要时可以拆成2条短消息发出每条优先控制在36字内只给第一层答案。"
return "尽量短,像群友临时接一句,不要长篇大论。"
@staticmethod
@@ -523,6 +524,24 @@ class AIAutoResponsePlugin(MessagePluginInterface):
clipped = first[:limit].rstrip(",、;;:")
return clipped
@staticmethod
def _split_reply_chunks(text: str, sentence_limit: int, char_limit: int, chunk_limit: int) -> List[str]:
parts = [item.strip() for item in re.split(r"(?<=[。!?!?;])", text) if item.strip()]
if not parts:
short = text.strip()
return [short[:char_limit].rstrip(",、;;:").strip()] if short else []
chunks: List[str] = []
for part in parts[:sentence_limit]:
current = part
if len(current) > char_limit:
current = current[:char_limit].rstrip(",、;;:")
if current:
chunks.append(current.strip())
if len(chunks) >= chunk_limit:
break
return chunks[:chunk_limit] or [text[:char_limit].strip()]
def _sync_member_memory(self, room_id: str, sender: str, sender_name: str, member_context: Dict) -> None:
if not member_context:
@@ -663,6 +682,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
f"[XIAONIU] SENT room={room} user={sender_name}/{sender} "
f"trigger={data.get('trigger_type', 'none')} "
f"mode={data.get('reply_mode', '')} "
f"chunks={data.get('chunk_count', 1)} "
f"len={data.get('response_len', 0)} "
f"reply={data.get('response_preview', '')}"
).strip()