feat: adaptive debounce and intent-driven quote trigger tuning

This commit is contained in:
2026-02-28 22:54:00 +08:00
parent 41c93f9456
commit 5a73aa34d2
3 changed files with 239 additions and 18 deletions

View File

@@ -1487,7 +1487,11 @@ class CustomerServiceAgent:
state.image_count = len(state.pending_image_urls)
self._sync_pending_quote_state(message.from_id, state)
if self._is_batch_finish_signal(customer_text):
if self._is_batch_finish_intent(
text=customer_text,
state=state,
has_incoming_urls=bool(incoming_urls),
):
quote_res = await self._quote_pending_images(state, message)
reply_text = quote_res.get("reply", "")
need_transfer = bool(quote_res.get("need_transfer"))
@@ -1509,7 +1513,11 @@ class CustomerServiceAgent:
if text_without_urls:
self._append_requirement(state, text_without_urls)
self._sync_pending_quote_state(message.from_id, state)
if self._is_batch_finish_signal(customer_text):
if self._is_batch_finish_intent(
text=customer_text,
state=state,
has_incoming_urls=False,
):
quote_res = await self._quote_pending_images(state, message)
reply_text = quote_res.get("reply", "")
need_transfer = bool(quote_res.get("need_transfer"))
@@ -2057,23 +2065,86 @@ class CustomerServiceAgent:
]
return any(k in text for k in finish_keywords)
def _is_batch_finish_intent(
self,
text: str,
state: ConversationState,
has_incoming_urls: bool,
) -> bool:
"""
语义结束识别:
- 显式口令:发完了/统一报价
- 隐式意图:询价/砍价
- 单图需求明确:如“这个门头上面的字做一下”可直接进入报价
"""
if not text:
return False
if self._is_batch_finish_signal(text):
return True
if has_incoming_urls:
return False
if not state.pending_image_urls:
return False
# 意图识别:询价/砍价通常意味着“可以报价了”
try:
from utils.intent_analyzer import detect_intent_embedding, detect_intent_keywords
intent = detect_intent_embedding(text) or detect_intent_keywords(text)
except Exception:
intent = ""
if intent in ("询价", "砍价"):
return True
msg = (text or "").strip()
if not msg:
return False
# 单图场景:客户给出明确加工指令,可直接报价
single_image_action_kw = (
"做一下", "改一下", "处理一下", "就这张", "按这个做", "照这个做",
"这个门头", "上面的字", "这个字", "这个图做", "能做吗",
)
multi_image_finish_kw = (
"就这些", "就这几张", "按这几张", "这几张一起做", "一起做一下",
"先按这些", "先按这几张", "直接报价", "现在报价", "看下报价",
"先报个总价", "总价多少", "一起多少钱", "先做这几张",
)
hold_kw = ("还有", "再发", "先等", "稍后", "等会", "回头")
if len(state.pending_image_urls) == 1:
if any(k in msg for k in single_image_action_kw) and not any(k in msg for k in hold_kw):
return True
elif len(state.pending_image_urls) >= 2:
if any(k in msg for k in multi_image_finish_kw) and not any(k in msg for k in hold_kw):
return True
return False
def _build_collect_ack(self, count: int) -> str:
if count <= 1:
return "收到这张图了,你还有图就继续发;发完我再一起给你报价。"
one_templates = [
"这张收到啦,还有图就继续发,我一起给你看。",
"图我看到了,后面还有就接着发,最后我一口价给你。",
"收到这张了,你有其他图也发来,我统一帮你算。",
]
return random.choice(one_templates)
templates = [
"收到,这边先记下了(已收{n}张)。你继续发,等你发完我再一起给你打包报价",
"的,当前这批先收到了(第{n}张)。还有图就继续发,发齐我一次性给你总价。",
"没问题,已记录到第{n}张。你把需求和图片都发完,我统一给你报更合适的价格",
"这几张我都收到了(现在{n}张)。还有的话继续发,我一起给你报",
"嘞,先看到{n}张了。你可以继续发,或者直接说“就这些”我现在就报价。",
"收到哈(共{n}。你还要补图就继续发,不补的话我现在也可以直接给价",
]
return random.choice(templates).format(n=count)
def _build_collect_remind(self, count: int) -> str:
if count <= 1:
return "需求我记下了。你如果还有图继续发,发完回我“发完了”,我给你报价。"
one_templates = [
"这个要求我记住了。你还有图就继续发,不补图我就按这张给你报价。",
"明白,这个需求我加上了。你继续发图也行,想直接报价也可以。",
"收到,我按你这个要求做。你要是就这张,我现在就能给你报价。",
]
return random.choice(one_templates)
templates = [
"需求我记下了(当前{n})。你继续发齐,发完回我“发完了”,我一次性给你总价。",
",这条需求也加上了(现在{n}张)。等你说发完,我立刻统一报价。",
"收到,这个要求我也记住了(共{n}张)。你发完我就给你打包价。",
"需求我记下了(当前{n}张)。你继续补图,或者直接说“就这些”我现在报价。",
"好,这个要求也加上了(现在{n}张)。不再补图的话我立刻给你打包价。",
"收到(共{n}张)。你还发就继续,不发的话我现在就给总价。",
]
return random.choice(templates).format(n=count)
@@ -2149,19 +2220,34 @@ class CustomerServiceAgent:
# 单图时不要使用“分图/这批/A-B方案”措辞避免客户误解为多图。
if len(results) == 1:
line = detail_lines[0].replace("图1", "这张:")
lines = [f"给你报下这张:{line.split('', 1)[1]}"]
heads = [
"这张我看过了,先给你报下:",
"这张可以做,价格给你报下:",
"看了这张图,报价如下:",
]
lines = [f"{random.choice(heads)}{line.split('', 1)[1]}"]
if req_hit:
lines.append(f"按你的需求另加{extra}元({req_hit})。")
lines.append(f"这张做下来共{single_total}元,可以的话我马上安排。")
tails = [
f"这张做下来共{single_total}元,定了我马上开工。",
f"合下来是{single_total}元,你点头我这边立刻安排。",
f"总价{single_total}元,可以的话我现在就给你做。",
]
lines.append(random.choice(tails))
return "\n".join(lines)
lines = ["先给你分图报下:"]
heads = [
"我先按这几张给你报一下:",
"这几张我都看过了,价格给你列一下:",
"我把每张价格先给你说清楚:",
]
lines = [random.choice(heads)]
lines.extend(detail_lines)
if req_hit:
lines.append(f"需求加价:+{extra}元({req_hit}")
option_line = f"可选:A按单张做共{single_total}B打包一起做{bundle_price}(更划算)。"
option_line = f"可选:按单张做{single_total}),或打包做({bundle_price},会更省一点)。"
lines.append(option_line)
lines.append("你定一个方案,我这边马上安排")
lines.append("你定一个,我这边马上开工")
return "\n".join(lines)
def _prepare_batch_intake(self, state: ConversationState) -> Dict[str, Any]: