tune xiaoniu reply brevity and flow thresholds
This commit is contained in:
@@ -15,8 +15,8 @@ endpoint = "chat/completions"
|
|||||||
api_key = "sk-XTWwXIgo2QMyX8AwBg0NQrxaDkvQiCX8rfylfmnHID5zdjMt"
|
api_key = "sk-XTWwXIgo2QMyX8AwBg0NQrxaDkvQiCX8rfylfmnHID5zdjMt"
|
||||||
model = "gpt-5.4"
|
model = "gpt-5.4"
|
||||||
timeout_seconds = 45
|
timeout_seconds = 45
|
||||||
temperature = 0.7
|
temperature = 0.35
|
||||||
max_tokens = 500
|
max_tokens = 120
|
||||||
stream = true
|
stream = true
|
||||||
|
|
||||||
[mode]
|
[mode]
|
||||||
@@ -39,24 +39,24 @@ casual_topic = 0.35
|
|||||||
|
|
||||||
[flow]
|
[flow]
|
||||||
enable_flow_state = true
|
enable_flow_state = true
|
||||||
flow_decay_per_minute = 8
|
flow_decay_per_minute = 12
|
||||||
idle_threshold = 20
|
idle_threshold = 30
|
||||||
warming_threshold = 40
|
warming_threshold = 55
|
||||||
engaged_threshold = 70
|
engaged_threshold = 90
|
||||||
at_bot_boost = 40
|
at_bot_boost = 40
|
||||||
question_boost = 30
|
question_boost = 30
|
||||||
followup_boost = 20
|
followup_boost = 20
|
||||||
topic_boost = 15
|
topic_boost = 8
|
||||||
returning_member_boost = 10
|
returning_member_boost = 6
|
||||||
response_accepted_boost = 15
|
response_accepted_boost = 10
|
||||||
ignored_reply_penalty = 20
|
ignored_reply_penalty = 20
|
||||||
over_reply_penalty = 15
|
over_reply_penalty = 22
|
||||||
night_penalty = 30
|
night_penalty = 30
|
||||||
max_bot_reply_streak = 3
|
max_bot_reply_streak = 2
|
||||||
|
|
||||||
[cooldown]
|
[cooldown]
|
||||||
group_reply_cooldown_sec = 45
|
group_reply_cooldown_sec = 90
|
||||||
same_user_followup_cooldown_sec = 10
|
same_user_followup_cooldown_sec = 18
|
||||||
night_silent_hours = ["01:00-07:30"]
|
night_silent_hours = ["01:00-07:30"]
|
||||||
|
|
||||||
[memory]
|
[memory]
|
||||||
@@ -69,6 +69,7 @@ qdrant_collection = "abot_xiaoniu_memory"
|
|||||||
ollama_base_url = "http://192.168.2.50:11434"
|
ollama_base_url = "http://192.168.2.50:11434"
|
||||||
embedding_model = "bge-m3"
|
embedding_model = "bge-m3"
|
||||||
vector_top_k = 5
|
vector_top_k = 5
|
||||||
|
max_context_memories = 2
|
||||||
vector_min_score = 0.65
|
vector_min_score = 0.65
|
||||||
vector_trigger_modes = ["returning_member", "long_absent_member", "qa_with_context", "reactivated_topic"]
|
vector_trigger_modes = ["returning_member", "long_absent_member", "qa_with_context", "reactivated_topic"]
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ContextBuilder:
|
|||||||
vector_memories: List[Dict],
|
vector_memories: List[Dict],
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
recent_lines = []
|
recent_lines = []
|
||||||
for item in recent_messages[-30:]:
|
for item in recent_messages[-8:]:
|
||||||
msg_sender = item.get("sender_name") or item.get("sender") or "未知成员"
|
msg_sender = item.get("sender_name") or item.get("sender") or "未知成员"
|
||||||
msg_content = item.get("content") or item.get("message") or ""
|
msg_content = item.get("content") or item.get("message") or ""
|
||||||
if msg_content:
|
if msg_content:
|
||||||
@@ -63,7 +63,7 @@ class ContextBuilder:
|
|||||||
if not vector_memories:
|
if not vector_memories:
|
||||||
return ""
|
return ""
|
||||||
lines = []
|
lines = []
|
||||||
for item in vector_memories[:5]:
|
for item in vector_memories[:2]:
|
||||||
summary = item.get("content_summary") or item.get("summary_text") or item.get("text") or ""
|
summary = item.get("content_summary") or item.get("summary_text") or item.get("text") or ""
|
||||||
memory_type = item.get("memory_type", "memory")
|
memory_type = item.get("memory_type", "memory")
|
||||||
if summary:
|
if summary:
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
)
|
)
|
||||||
return False, "empty_response"
|
return False, "empty_response"
|
||||||
|
|
||||||
|
response = self._finalize_reply(response, reply_mode)
|
||||||
|
|
||||||
await bot.send_text_message(room_id, response, sender)
|
await bot.send_text_message(room_id, response, sender)
|
||||||
self.last_reply_at[room_id] = time.time()
|
self.last_reply_at[room_id] = time.time()
|
||||||
self.flow_manager.note_bot_reply(room_id)
|
self.flow_manager.note_bot_reply(room_id)
|
||||||
@@ -314,7 +316,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
return (current_ts - last_room_reply) >= room_cd
|
return (current_ts - last_room_reply) >= room_cd
|
||||||
|
|
||||||
def _build_user_prompt(self, context: Dict, memory_hints: Dict) -> str:
|
def _build_user_prompt(self, context: Dict, memory_hints: Dict) -> str:
|
||||||
recent_text = "\n".join(context.get("recent_messages", [])[-20:]) or "暂无"
|
recent_text = "\n".join(context.get("recent_messages", [])[-8:]) or "暂无"
|
||||||
reply_mode = context.get("reply_mode", "social_short")
|
reply_mode = context.get("reply_mode", "social_short")
|
||||||
length_rule = self._build_length_rule(reply_mode)
|
length_rule = self._build_length_rule(reply_mode)
|
||||||
return (
|
return (
|
||||||
@@ -333,6 +335,11 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
f"4. 如果信息不足,不要硬编。\n"
|
f"4. 如果信息不足,不要硬编。\n"
|
||||||
f"5. 输出最终可直接发到群里的内容,不要解释你的思路。\n"
|
f"5. 输出最终可直接发到群里的内容,不要解释你的思路。\n"
|
||||||
f"6. {length_rule}\n"
|
f"6. {length_rule}\n"
|
||||||
|
f"7. 优先直接回应“当前发言”本身,不要被较早上下文带跑。\n"
|
||||||
|
f"8. 成员记忆和向量召回只有在与当前问题直接相关时才允许使用,否则忽略。\n"
|
||||||
|
f"9. 如果你不确定自己是否理解对了,就宁可不展开,只回很短。\n"
|
||||||
|
f"10. 把这次回复当作真人聊天里的第一反应,先只给第一层结论,不要主动补第二层解释。\n"
|
||||||
|
f"11. 如果一句话已经够了,就立刻停,不要为了完整而补充。\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -343,16 +350,42 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
|||||||
response = re.sub(r"\n{3,}", "\n\n", response)
|
response = re.sub(r"\n{3,}", "\n\n", response)
|
||||||
return response[:500].strip()
|
return response[:500].strip()
|
||||||
|
|
||||||
|
def _finalize_reply(self, response: str, reply_mode: str) -> str:
|
||||||
|
text = (response or "").strip()
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
text = re.sub(r"\s+", " ", text)
|
||||||
|
text = text.replace("\n", " ").strip()
|
||||||
|
|
||||||
|
if reply_mode == "social_short":
|
||||||
|
text = self._take_first_sentence(text, 12)
|
||||||
|
elif reply_mode == "qa_fast":
|
||||||
|
text = self._take_first_sentence(text, 28)
|
||||||
|
elif reply_mode == "qa_with_context":
|
||||||
|
text = self._take_first_sentence(text, 36)
|
||||||
|
else:
|
||||||
|
text = self._take_first_sentence(text, 24)
|
||||||
|
return text.strip()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_length_rule(reply_mode: str) -> str:
|
def _build_length_rule(reply_mode: str) -> str:
|
||||||
if reply_mode == "social_short":
|
if reply_mode == "social_short":
|
||||||
return "默认只回一句短话,最好控制在2到12个字,除非非常不自然。"
|
return "默认只回一句短话,最好控制在2到8个字,除非非常不自然。"
|
||||||
if reply_mode == "qa_fast":
|
if reply_mode == "qa_fast":
|
||||||
return "尽量只回1句话,必要时最多2句,先给结论,不要展开成长教程。"
|
return "尽量只回1句话,总长度优先控制在28字内,先给结论,不要主动补解释。"
|
||||||
if reply_mode == "qa_with_context":
|
if reply_mode == "qa_with_context":
|
||||||
return "优先控制在1到2句,除非对方明显在等详细步骤。"
|
return "优先控制在1句话,必要时最多2句,总长度优先控制在36字内,只给第一层答案。"
|
||||||
return "尽量短,像群友临时接一句,不要长篇大论。"
|
return "尽量短,像群友临时接一句,不要长篇大论。"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _take_first_sentence(text: str, limit: int) -> str:
|
||||||
|
parts = re.split(r"(?<=[。!?!?;;])", text)
|
||||||
|
first = parts[0].strip() if parts and parts[0].strip() else text.strip()
|
||||||
|
if len(first) <= limit:
|
||||||
|
return first
|
||||||
|
clipped = first[:limit].rstrip(",,、;;::")
|
||||||
|
return clipped
|
||||||
|
|
||||||
|
|
||||||
def _sync_member_memory(self, room_id: str, sender: str, sender_name: str, member_context: Dict) -> None:
|
def _sync_member_memory(self, room_id: str, sender: str, sender_name: str, member_context: Dict) -> None:
|
||||||
if not member_context:
|
if not member_context:
|
||||||
|
|||||||
@@ -11,10 +11,13 @@
|
|||||||
|
|
||||||
你的表达偏好:
|
你的表达偏好:
|
||||||
- 能一句说清就别说三句
|
- 能一句说清就别说三句
|
||||||
|
- 默认宁可短一点,也不要展开过头
|
||||||
|
- 默认只给第一反应,不要一次把后续解释全说完
|
||||||
- 避免客服腔、教程腔、模板腔
|
- 避免客服腔、教程腔、模板腔
|
||||||
- 除非很有必要,不要长篇大论
|
- 除非很有必要,不要长篇大论
|
||||||
- 允许少量语气词,但不要太油腻
|
- 允许少量语气词,但不要太油腻
|
||||||
- 面对回归成员时,可以表现出轻微熟悉感,但不要直接暴露细粒度历史记录
|
- 面对回归成员时,可以表现出轻微熟悉感,但不要直接暴露细粒度历史记录
|
||||||
|
- 如果当前消息信息不足,就少说,不要自顾自发挥
|
||||||
|
|
||||||
你的边界:
|
你的边界:
|
||||||
- 不要假装知道不存在的上下文
|
- 不要假装知道不存在的上下文
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class ResponsePlanner:
|
|||||||
return "social_short"
|
return "social_short"
|
||||||
if trigger.get("is_returning_member"):
|
if trigger.get("is_returning_member"):
|
||||||
return "social_short"
|
return "social_short"
|
||||||
return "social_short" if flow_state in {"warming", "engaged"} else "refuse_or_skip"
|
return "social_short" if flow_state in {"deep_engaged"} else "refuse_or_skip"
|
||||||
|
|
||||||
def should_reply(self, trigger: Dict, flow_state: str, allow_proactive: bool) -> bool:
|
def should_reply(self, trigger: Dict, flow_state: str, allow_proactive: bool) -> bool:
|
||||||
if trigger.get("should_respond"):
|
if trigger.get("should_respond"):
|
||||||
return True
|
return True
|
||||||
if not allow_proactive:
|
if not allow_proactive:
|
||||||
return False
|
return False
|
||||||
return flow_state in {"warming", "engaged", "deep_engaged"} and trigger.get("priority", 0) >= 0.35
|
return flow_state in {"deep_engaged"} and trigger.get("priority", 0) >= 0.65
|
||||||
|
|||||||
Reference in New Issue
Block a user