落地转人工:三次补问或明确接管后出入口,真正转人工后冻结本票并允许新开询价。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user