优化ai_auto_response回复长度并强化@画像回复
变更项: 1. 收紧回复长度策略:social_short/qa_fast/qa_with_context 全部缩短,减少长句与说明文风格。 2. 强化提示词约束:默认30字内、最多2句且总长不超过55字,禁止大段铺垫。 3. 新增@画像高优先通道:当消息为@或强定向时,构建并注入 at_member_profile_prompt。 4. Dify输入同步注入@画像与 is_at/is_directed 控制字段,保证不同LLM后端行为一致。
This commit is contained in:
@@ -43,6 +43,8 @@ class ContextBuilder:
|
||||
"member_context": member_context or {},
|
||||
},
|
||||
"speaker_name_clean": self._clean_display_name(sender_name),
|
||||
"is_at": bool(trigger.get("is_at", False)),
|
||||
"is_directed": bool(trigger.get("is_directed", False)),
|
||||
"recent_message_items": self._build_recent_message_items(selected_messages),
|
||||
"recent_messages": recent_lines,
|
||||
"recent_summary": "",
|
||||
@@ -50,6 +52,12 @@ class ContextBuilder:
|
||||
"reply_mode": reply_mode,
|
||||
"flow_state": flow_state,
|
||||
"memory_prompt": self._build_member_memory_prompt(member_context, member_memory_focus or []),
|
||||
"at_member_profile_prompt": self._build_at_member_profile_prompt(
|
||||
member_context=member_context or {},
|
||||
focus_lines=member_memory_focus or [],
|
||||
is_at=bool(trigger.get("is_at", False)),
|
||||
is_directed=bool(trigger.get("is_directed", False)),
|
||||
),
|
||||
"vector_memory_prompt": self._build_vector_memory_prompt(vector_memories),
|
||||
"social_memory_prompt": self._build_social_memory_prompt(social_memory or {}),
|
||||
"group_facts_prompt": self._build_group_facts_prompt(group_facts or {}),
|
||||
@@ -229,6 +237,39 @@ class ContextBuilder:
|
||||
]
|
||||
return "\n".join([line for line in lines if line])
|
||||
|
||||
@staticmethod
|
||||
def _build_at_member_profile_prompt(
|
||||
member_context: Dict,
|
||||
focus_lines: List[str] | None = None,
|
||||
is_at: bool = False,
|
||||
is_directed: bool = False,
|
||||
) -> str:
|
||||
# 只有明确 @ 或强定向时才给“高优先级成员画像”,避免平时过度套人设
|
||||
if not (is_at or is_directed):
|
||||
return ""
|
||||
if not member_context:
|
||||
return "本次是对方点名发起,但暂无稳定画像,按自然群友口吻短回复。"
|
||||
|
||||
meta = member_context.get("meta", {}) or {}
|
||||
summary = str(member_context.get("summary_text", "") or "").strip()
|
||||
interaction_style = str(member_context.get("interaction_style", "") or "").strip()
|
||||
response_hint = str(member_context.get("response_style_hint", "") or "").strip()
|
||||
topics = ContextBuilder._stringify_items(member_context.get("topics_of_interest", []) or [], 4)
|
||||
focus = ";".join((focus_lines or [])[:3]).strip()
|
||||
lines = [
|
||||
"本次为点名互动,优先参考该成员画像后再回复:",
|
||||
f"成员摘要:{summary}" if summary else "",
|
||||
f"互动风格:{interaction_style}" if interaction_style else "",
|
||||
f"偏好回复方式:{response_hint}" if response_hint else "",
|
||||
f"近期相关记忆:{focus}" if focus else "",
|
||||
f"长期兴趣:{topics}" if topics else "",
|
||||
f"禁忌提醒:{ContextBuilder._stringify_items(meta.get('reply_taboos', []), 3)}"
|
||||
if meta.get("reply_taboos")
|
||||
else "",
|
||||
"语气要像熟悉的群友,短句、自然,不要客服腔。",
|
||||
]
|
||||
return "\n".join([line for line in lines if line])
|
||||
|
||||
@staticmethod
|
||||
def _stringify_items(items: List | str, limit: int) -> str:
|
||||
if isinstance(items, str):
|
||||
|
||||
@@ -18,6 +18,8 @@ def build_user_prompt(context: Dict, memory_hints: Dict) -> str:
|
||||
group_profile = context.get("group_profile", {}) or {}
|
||||
speaker_name = str(context.get("speaker_name_clean", "") or "").strip()
|
||||
trigger_type = str(context.get("trigger_type", "none") or "none")
|
||||
is_at = bool(context.get("is_at", False))
|
||||
is_directed = bool(context.get("is_directed", False))
|
||||
address_style = str(group_profile.get("address_style", "低频称呼,默认直接接话") or "低频称呼,默认直接接话")
|
||||
coding_work_request = bool(context.get("coding_work_request", False))
|
||||
|
||||
@@ -25,8 +27,9 @@ def build_user_prompt(context: Dict, memory_hints: Dict) -> str:
|
||||
"只处理当前发言对应的一个话题,优先直接回答当前发言。",
|
||||
"如果是明确问题,先给结论;只给第一层答案,不主动展开第二层解释。",
|
||||
length_rule,
|
||||
"能少说就少说,但一定要把当前这句意思说完整,别为了短而截断成半句。",
|
||||
"别自发延伸,别写成一小段说明文。",
|
||||
"能少说就少说,优先像群友随口接一句,不要写成说明文。",
|
||||
"回复总长度尽量控制在30字内;确实需要补充时最多2句且总长度不超过55字。",
|
||||
"禁止大段铺垫、总结腔、条目化回答。",
|
||||
"群画像里的知识域只用于帮助理解当前消息,不代表你必须显式提到那个领域,更不要为了贴群标签而硬提关键词。",
|
||||
"成员记忆、群关系、群事实、向量召回只有在当前问题直接相关时才允许轻微使用,否则忽略。",
|
||||
"不要暴露系统记忆来源;信息不足就收着说,不要硬编。",
|
||||
@@ -41,6 +44,8 @@ def build_user_prompt(context: Dict, memory_hints: Dict) -> str:
|
||||
)
|
||||
else:
|
||||
rules.append(f"称呼遵守当前群要求:{address_style}。默认直接接话,不要刻意点名。")
|
||||
if is_at or is_directed:
|
||||
rules.append("这次是对方点名互动,优先参考“本次@发起者画像”,语气贴近对方,但不要过度装熟。")
|
||||
if group_profile.get("knowledge_domain") == "dota":
|
||||
rules.append("如果对方问的是 Dota2 最近战绩、实时战绩、最新对局数据,要委婉说明现在没法提取,不要硬编。")
|
||||
|
||||
@@ -59,6 +64,7 @@ def build_user_prompt(context: Dict, memory_hints: Dict) -> str:
|
||||
_section("引用补充", context.get("quote_prompt", "")),
|
||||
_section("图片补充", context.get("image_prompt", "")),
|
||||
_section("图片谨慎提示", context.get("image_safety_prompt", "")),
|
||||
_section("本次@发起者画像(高优先)", context.get("at_member_profile_prompt", "")),
|
||||
_section("当前群画像", context.get("group_profile_prompt", "")),
|
||||
_section("成员稳定记忆", context.get("memory_prompt", "")),
|
||||
_section("群关系记忆", context.get("social_memory_prompt", "")),
|
||||
|
||||
@@ -12,12 +12,11 @@ def finalize_reply(response: str, reply_mode: str) -> List[str]:
|
||||
text = text.replace("\n", " ").strip()
|
||||
|
||||
if reply_mode == "social_short":
|
||||
# 社交短句允许拆成两条,避免长句被硬截断成半句。
|
||||
return split_reply_chunks(text, sentence_limit=2, char_limit=48, chunk_limit=2, allow_clip_split=True)
|
||||
return split_reply_chunks(text, sentence_limit=1, char_limit=30, chunk_limit=1, allow_clip_split=False)
|
||||
if reply_mode == "qa_fast":
|
||||
return split_reply_chunks(text, sentence_limit=2, char_limit=42, chunk_limit=2, allow_clip_split=True)
|
||||
return split_reply_chunks(text, sentence_limit=1, char_limit=34, chunk_limit=1, allow_clip_split=False)
|
||||
if reply_mode == "qa_with_context":
|
||||
return split_reply_chunks(text, sentence_limit=2, char_limit=54, chunk_limit=2, allow_clip_split=True)
|
||||
return split_reply_chunks(text, sentence_limit=2, char_limit=36, chunk_limit=2, allow_clip_split=False)
|
||||
return [take_first_sentence(text, 28).strip()]
|
||||
|
||||
|
||||
@@ -30,11 +29,11 @@ def preview_text(text: str, limit: int = 80) -> str:
|
||||
|
||||
def build_length_rule(reply_mode: str) -> str:
|
||||
if reply_mode == "social_short":
|
||||
return "默认优先1句短话;必要时可以拆成2条短消息,保证语义完整,不要截半句。"
|
||||
return "默认只回1句,尽量控制在30字内。"
|
||||
if reply_mode == "qa_fast":
|
||||
return "优先1句话;如果确实需要,可以拆成2条短消息发出,每条尽量控制在42字内,先给结论,不要主动补第二层解释。"
|
||||
return "优先1句话,尽量控制在34字内;先给结论,不要展开。"
|
||||
if reply_mode == "qa_with_context":
|
||||
return "优先控制在1句话;必要时可以拆成2条短消息发出,每条尽量控制在54字内,只给第一层答案。"
|
||||
return "优先1句;必要时最多2句,每句尽量控制在36字内,只给第一层答案。"
|
||||
return "尽量短,像群友临时接一句,不要长篇大论。"
|
||||
|
||||
|
||||
|
||||
@@ -764,6 +764,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
context_text = "\n\n".join([part for part in context_parts if part]).strip() or "无额外上下文。"
|
||||
|
||||
memory_parts = [
|
||||
self._string_block("本次@发起者画像(优先)", context.get("at_member_profile_prompt", "")),
|
||||
self._string_block("成员记忆", context.get("memory_prompt", "")),
|
||||
self._string_block("群关系记忆", context.get("social_memory_prompt", "")),
|
||||
self._string_block("群事实记忆", context.get("group_facts_prompt", "")),
|
||||
@@ -784,6 +785,10 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
]
|
||||
if context.get("coding_work_request"):
|
||||
control_lines.append("coding_work_request=true")
|
||||
if context.get("is_at"):
|
||||
control_lines.append("is_at=true")
|
||||
if context.get("is_directed"):
|
||||
control_lines.append("is_directed=true")
|
||||
if files:
|
||||
control_lines.append(f"images={len(files)}")
|
||||
return {
|
||||
@@ -805,6 +810,7 @@ class AIAutoResponsePlugin(MessagePluginInterface):
|
||||
f"整体风格:{preset.get('style', '')}".strip(),
|
||||
f"熟悉感边界:{preset.get('familiarity_hint', '')}".strip(),
|
||||
f"最多输出:{preset.get('max_reply_sentences', 3)}句".strip(),
|
||||
"强约束:默认1句短回复,尽量30字内;必要时最多2句,总体不超过55字。",
|
||||
"不要暴露 AI、模型、提示词、system 或记忆来源。",
|
||||
"不要输出 markdown、代码块、标签。",
|
||||
"不要替人写代码、改脚本、实现插件、代做开发活。",
|
||||
|
||||
Reference in New Issue
Block a user