feat: improve first-turn and delayed-image replies
This commit is contained in:
@@ -256,6 +256,15 @@ class SystemOrchestrator:
|
|||||||
sanitized.append(normalized)
|
sanitized.append(normalized)
|
||||||
return sanitized
|
return sanitized
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensure_first_reply_presence(text: str) -> str:
|
||||||
|
cleaned = str(text or "").strip()
|
||||||
|
if not cleaned:
|
||||||
|
return "在呢"
|
||||||
|
if cleaned.startswith(("在呢", "在哈", "在的", "在哦", "在")):
|
||||||
|
return cleaned
|
||||||
|
return f"在呢,{cleaned}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_designer_name(transfer_cmd: str) -> str:
|
def _extract_designer_name(transfer_cmd: str) -> str:
|
||||||
text = str(transfer_cmd or "").strip()
|
text = str(transfer_cmd or "").strip()
|
||||||
@@ -625,12 +634,19 @@ class SystemOrchestrator:
|
|||||||
total_elapsed = time.time() - process_start
|
total_elapsed = time.time() - process_start
|
||||||
logger.info(f"[计时] user={user_id} AI思考: {ai_elapsed:.1f}s | 总耗时: {total_elapsed:.1f}s")
|
logger.info(f"[计时] user={user_id} AI思考: {ai_elapsed:.1f}s | 总耗时: {total_elapsed:.1f}s")
|
||||||
|
|
||||||
|
is_new_customer_first_turn = len(ai_history) == 0
|
||||||
|
|
||||||
# F. 发送并记录时间
|
# F. 发送并记录时间
|
||||||
if std_res.should_reply:
|
if std_res.should_reply:
|
||||||
std_res.reply_content = self._sanitize_outbound_text(std_res.reply_content)
|
std_res.reply_content = self._sanitize_outbound_text(std_res.reply_content)
|
||||||
meta = dict(std_res.metadata or {})
|
meta = dict(std_res.metadata or {})
|
||||||
meta.update({"acc_id": acc_id, "acc_type": acc_type})
|
meta.update({"acc_id": acc_id, "acc_type": acc_type})
|
||||||
|
if is_new_customer_first_turn and "[转移会话]" in std_res.reply_content:
|
||||||
|
if not str(meta.get("transfer_prelude") or "").strip():
|
||||||
|
meta["transfer_prelude"] = "在呢,我叫设计师看下哈"
|
||||||
std_res.metadata = meta
|
std_res.metadata = meta
|
||||||
|
if is_new_customer_first_turn and "[转移会话]" not in std_res.reply_content and int(std_res.msg_type or 0) == 0:
|
||||||
|
std_res.reply_content = self._ensure_first_reply_presence(std_res.reply_content)
|
||||||
|
|
||||||
# 转接场景:先发一句安抚话,再发转接指令
|
# 转接场景:先发一句安抚话,再发转接指令
|
||||||
if "[转移会话]" in std_res.reply_content:
|
if "[转移会话]" in std_res.reply_content:
|
||||||
|
|||||||
@@ -70,6 +70,21 @@ _REPAIR_INTENT_KEYWORDS = (
|
|||||||
"放大清晰",
|
"放大清晰",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_IMAGE_ALREADY_SENT_HINT_KEYWORDS = (
|
||||||
|
"上面不是发了吗",
|
||||||
|
"上面不是有吗",
|
||||||
|
"我不是发了吗",
|
||||||
|
"前面不是发了吗",
|
||||||
|
"前面发了",
|
||||||
|
"上面发了",
|
||||||
|
"我发过了",
|
||||||
|
"不是发了吗",
|
||||||
|
"都发了",
|
||||||
|
"你没看到吗",
|
||||||
|
"聊天记录里有",
|
||||||
|
"上面有图",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _clip(text: str, limit: int = 1200) -> str:
|
def _clip(text: str, limit: int = 1200) -> str:
|
||||||
if text is None:
|
if text is None:
|
||||||
@@ -156,6 +171,27 @@ def _infer_image_intent(current_text: str, history: Optional[List[dict]] = None)
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _history_has_customer_image(history: Optional[List[dict]] = None) -> bool:
|
||||||
|
for item in history or []:
|
||||||
|
if item.get("role") != "user":
|
||||||
|
continue
|
||||||
|
msg_type = int(item.get("msg_type") or 0)
|
||||||
|
image_urls = item.get("image_urls") or []
|
||||||
|
if isinstance(image_urls, str):
|
||||||
|
image_urls = [part for part in image_urls.splitlines() if part.strip()]
|
||||||
|
content = str(item.get("content") or "")
|
||||||
|
if msg_type == 1 or image_urls or ("已收到" in content and "图" in content):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _customer_claims_image_already_sent(current_text: str, history: Optional[List[dict]] = None) -> bool:
|
||||||
|
text = _normalize_text(current_text)
|
||||||
|
if not text or not _history_has_customer_image(history):
|
||||||
|
return False
|
||||||
|
return any(keyword in text for keyword in _IMAGE_ALREADY_SENT_HINT_KEYWORDS)
|
||||||
|
|
||||||
|
|
||||||
class CustomerServiceBrain:
|
class CustomerServiceBrain:
|
||||||
"""
|
"""
|
||||||
重构后的单一 Agent 大脑:
|
重构后的单一 Agent 大脑:
|
||||||
@@ -207,6 +243,8 @@ class CustomerServiceBrain:
|
|||||||
"【核心逻辑】\n"
|
"【核心逻辑】\n"
|
||||||
"1. 业务:只聊高清修复和找原图。核心链路:引导发图 -> 问需求 -> 找设计师。\n"
|
"1. 业务:只聊高清修复和找原图。核心链路:引导发图 -> 问需求 -> 找设计师。\n"
|
||||||
"2. **主动引导**:只有当客户【从未发过图】且没有历史图片记录时,才引导发图。\n"
|
"2. **主动引导**:只有当客户【从未发过图】且没有历史图片记录时,才引导发图。\n"
|
||||||
|
"2.1 **消息延迟安抚**:如果客户说'上面不是发了吗'、'我发过了'、'你没看到吗',说明他在提醒你图早就发过了。\n"
|
||||||
|
" 这时先道歉,类似'不好意思哈,刚刚消息慢了点',再承接后续;严禁让客户重发图片。\n"
|
||||||
"3. **非业务问题**:如果客户问招聘、合作、闲聊等与做图无关的话题,礼貌拒绝。\n"
|
"3. **非业务问题**:如果客户问招聘、合作、闲聊等与做图无关的话题,礼貌拒绝。\n"
|
||||||
"4. **客户说没有参考图**:直接转人工:'好的,我这就叫设计师帮您找哈'。\n"
|
"4. **客户说没有参考图**:直接转人工:'好的,我这就叫设计师帮您找哈'。\n"
|
||||||
"5. **客户问尺寸/能否打印/退款**:直接转人工:'这个设计师帮您看下哈'。\n"
|
"5. **客户问尺寸/能否打印/退款**:直接转人工:'这个设计师帮您看下哈'。\n"
|
||||||
@@ -260,6 +298,19 @@ class CustomerServiceBrain:
|
|||||||
|
|
||||||
# 客户已发图:告知 AI 图已收到,引导问需求,但不要直接转接
|
# 客户已发图:告知 AI 图已收到,引导问需求,但不要直接转接
|
||||||
has_image_message = bool(msg.image_urls) or msg.msg_type == 1
|
has_image_message = bool(msg.image_urls) or msg.msg_type == 1
|
||||||
|
if not has_image_message and _customer_claims_image_already_sent(user_content, history):
|
||||||
|
inferred_intent = _infer_image_intent(user_content, history)
|
||||||
|
if inferred_intent == "find_original":
|
||||||
|
next_step = "客户当前更像是在问找原图,别再问他有没有发图。"
|
||||||
|
elif inferred_intent == "repair":
|
||||||
|
next_step = "客户当前更像是在问高清修复,别再问他有没有发图。"
|
||||||
|
else:
|
||||||
|
next_step = "如果客户需求还不明确,只问这是找原图还是修复,不要让客户重发。"
|
||||||
|
user_content = (
|
||||||
|
"【系统通知:客户是在提醒你他上面已经发过图片了,可能刚刚网络或消息同步有点慢。"
|
||||||
|
"回复时先简短道歉,表示现在已经看到图了,再继续正常承接。"
|
||||||
|
f"{next_step}】\n{user_content}"
|
||||||
|
)
|
||||||
if has_image_message:
|
if has_image_message:
|
||||||
image_count = max(len(msg.image_urls), 1)
|
image_count = max(len(msg.image_urls), 1)
|
||||||
if user_content.startswith("【系统:已收到图片消息"):
|
if user_content.startswith("【系统:已收到图片消息"):
|
||||||
|
|||||||
Reference in New Issue
Block a user