落地转人工:三次补问或明确接管后出入口,真正转人工后冻结本票并允许新开询价。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 16:42:13 +08:00
co-authored by Cursor
parent 26dd5d72a1
commit 7eedf28e8d
23 changed files with 1499 additions and 132 deletions
+118 -2
View File
@@ -81,7 +81,7 @@ BTN_NEGOTIATE_KEY = "negotiate"
BTN_COPY_TICKET = "复制工单号"
BTN_COPY_TICKET_KEY = "copy_ticket"
_WO_NO = re.compile(r"WO\d{12}", re.IGNORECASE)
_WO_NO = re.compile(r"(?:WO|VT)\d{12}", re.IGNORECASE)
# 仅给无网 stub / 工单号回填用,正式续办意图由 DeepSeek A 判断,不要求销售说这些词。
_CONTINUE_HINT = (
"继续",
@@ -101,6 +101,10 @@ _CONTINUE_HINT = (
ASK_TRANSPORT = "请补充运输方式:海运 / 空运 / 陆运。"
HANDOFF_ALREADY_COLLAB = "该工单已拉产品进群协同。"
HANDOFF_CLARIFY_REASON = "参数提取不全(连续 3 次反问仍不完整)"
HANDOFF_INTENT_REASON = "已识别您希望转人工处理"
CLARIFY_LIMIT_HINT = "连续 3 次反问仍不完整将触发转人工"
# 私聊图片询价:先回识别中;看不清/失败与补问拼成一条,销售侧不加 [test]。
IMAGE_RECOGNIZING = "正在识别图片"
IMAGE_UNREADABLE = "图片没看清询价信息,请换一张更清楚的,或直接打字。"
@@ -268,6 +272,28 @@ def _land_field_line(key: str, value: str, *, required: bool, example: str = "")
return f"{label}({mark}):"
def clarify_progress_line(retry: int) -> str:
"""补问次数提醒:参数提取不全(1/3)。"""
n = int(retry or 0)
if n < 1:
n = 1
if n > 3:
n = 3
return f"参数提取不全({n}/3)。{CLARIFY_LIMIT_HINT}"
def with_clarify_progress(body: str, retry: int) -> str:
"""把次数提醒顶在补问正文前面。第一次补问(retry=0)不打(1/3)。"""
text = (body or "").strip()
n = int(retry or 0)
if n < 1:
return text
head = clarify_progress_line(n)
if not text:
return head
return head + "\n" + text
def ask_land_fields(
*,
facts: dict[str, str] | None = None,
@@ -563,7 +589,7 @@ def canonical_button(raw: str) -> str:
def extract_work_order_no(text: str) -> str:
"""从任意原话里抽出 WO + 12 位数字;没有则空。不判断意图。"""
"""从任意原话里抽出 WO/VT + 12 位数字;没有则空。不判断意图。"""
hit = _WO_NO.search(text or "")
return hit.group(0).upper() if hit else ""
@@ -1221,6 +1247,96 @@ def sea_tms_miss_wecom_payload(*, work_order_no: str, action_url: str = "") -> d
return {"msgtype": "template_card", "template_card": card}
def handoff_air_hint(*, work_order_no: str) -> str:
"""空运转人工:只发文字,不建群。"""
no = (work_order_no or "").strip()
if no:
return f"触发转人工,请手动把航线和智能体拉进原有空运群,并发送工单号 {no}。"
return "触发转人工,请手动把航线和智能体拉进原有空运群,并发送工单号。"
def handoff_offer_text(*, reason: str, transport_mode: str) -> str:
"""海运/陆运转人工卡片的文字兜底(点卡或原样回复按钮均可)。"""
why = (reason or "").strip() or HANDOFF_INTENT_REASON
mode = (transport_mode or "").strip()
lines = [why]
if mode:
lines.append(f"运输方式:{mode}")
lines.append("是否拉产品人员进群协同?")
lines.append(BTN_PULL_COLLAB)
return "\n".join(lines)
def handoff_offer_wecom_payload(*, reason: str, task_token: str) -> dict[str, Any]:
"""海运/陆运一张卡、一个按钮:拉产品进群协同。"""
token = (task_token or "handoff").strip() or "handoff"
why = (reason or "").strip() or HANDOFF_INTENT_REASON
return {
"msgtype": "template_card",
"template_card": {
"card_type": "button_interaction",
"source": {"desc": "转人工", "desc_color": 0},
"main_title": {"title": why, "desc": "是否拉产品人员进群协同?"},
"task_id": card_task_id("handoff", token),
"button_list": [
{
"text": BTN_PULL_COLLAB,
"style": 1,
"key": f"{BTN_PULL_COLLAB_KEY}:{token}",
}
],
},
}
def handoff_group_brief(
*,
work_order_no: str,
facts: dict[str, str] | None,
business_line: str,
product_names: list[str] | None = None,
sales_name: str = "",
airline_names: list[str] | None = None,
) -> str:
"""
转人工群摘要:@人 +「工单号转人工」+ 已整理询价;未整理与协同全部为 -。
不写 TMS、不写模板。
"""
from agent.policy.handoff_human import collab_keys_for_handoff, transport_label
from agent.schema.field_validate import collab_display_name
no = (work_order_no or "").strip()
line = (business_line or "").strip().upper()
mode = transport_label(line) or "海运"
names: list[str] = []
if line == "AIR":
if (sales_name or "").strip():
names.append(sales_name.strip())
for n in airline_names or []:
if (n or "").strip() and n.strip() not in names:
names.append(n.strip())
else:
for n in product_names or []:
if (n or "").strip() and n.strip() not in names:
names.append(n.strip())
at = " ".join(f"@{n}" for n in names)
src = dict(facts or {})
field_lines = inquiry_field_lines(work_order_no=no, facts=src, transport_mode=mode)
body = field_lines[1:] if field_lines else []
lines: list[str] = []
if at:
lines.append(at)
lines.append(f"{no}转人工")
lines.append("询价字段:")
lines.extend(body)
lines.append("")
lines.append("协同补充字段:")
for key in collab_keys_for_handoff(src, line):
label = collab_display_name(key, line)
lines.append(f"{label}:-")
return "\n".join(lines)
def _role_label(name: str, role: str) -> str:
"""姓名前加岗位,避免「销售甲、乙」被读成两人都是销售。已带岗位前缀的不再重复加。"""
text = (name or "").strip()