Files
xieyi-weixin-/order_intent.py
2026-09-28 10:02:07 +08:00

97 lines
3.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""订单 / 快递意图识别。
只有客户在问订单、快递、物流、审核这类问题时,才把当前会话上下文交给订单 MCP。
否则每条消息都拉起一个只读订单服务子进程,既浪费,也让模型在闲聊时多了一个
可以误调用的工具。
关键词可以在 ``ai_config.AI_ORDER_INTENT_KEYWORDS`` 覆盖;留空则使用内置列表。
"""
from __future__ import annotations
import re
from typing import Any, Iterable
DEFAULT_ORDER_KEYWORDS: tuple[str, ...] = (
"快递", "物流", "订单", "单号", "运单", "快件", "包裹",
"发货", "发出", "寄出", "寄了", "寄过", "邮寄", "发了吗", "发没发",
"到哪", "到了吗", "到货", "到没到", "什么时候到", "啥时候到", "几天到",
"什么时候发", "啥时候发", "什么时候寄", "啥时候寄",
"签收", "派送", "派件", "配送", "送到", "收到药", "药到", "药寄", "药发",
"审核", "审核通过", "审没审", "过审", "支付审核", "处方审核",
"顺丰", "京东", "圆通", "中通", "申通", "韵达", "邮政", "ems", "极兔", "德邦",
"退款", "退货", "拒收", "改地址", "换地址", "地址写错",
)
# 客户在被要求“确认是哪一笔”之后的短回复,本身不含订单关键词,但仍属于订单话题。
_FOLLOW_UP_RE = re.compile(
r"^(第?[一二三两1-9]\s*(个|笔|单|次)?|最近(的|那)?(一)?(笔|个|单)?|上(一|次)(的|那)?(笔|个|单)?|"
r"(那|这)(笔|个|单)|都|全部|两个都|\d{1,2}月\d{1,2}(号|日)?(的|那)?(笔|个|单)?|"
r"[A-Za-z]{0,4}\d{6,}|(是|对|嗯|好)的?|不是|另一(个|笔))[。!!??~~,,\s]*$"
)
def _keywords() -> tuple[str, ...]:
try:
import ai_config
raw = getattr(ai_config, "AI_ORDER_INTENT_KEYWORDS", None)
except Exception:
raw = None
if isinstance(raw, (list, tuple)):
cleaned = tuple(str(item).strip().lower() for item in raw if str(item).strip())
if cleaned:
return cleaned
return DEFAULT_ORDER_KEYWORDS
def _normalize(text: Any) -> str:
return re.sub(r"\s+", "", str(text or "")).lower()
def matches_order_keywords(text: Any, keywords: Iterable[str] | None = None) -> bool:
body = _normalize(text)
if not body:
return False
return any(word and word in body for word in (keywords or _keywords()))
def _history_texts(history: Any, limit: int = 4) -> list[str]:
if not isinstance(history, list):
return []
texts = []
for item in history[-limit:]:
if isinstance(item, dict):
texts.append(str(item.get("content") or ""))
else:
texts.append(str(item or ""))
return texts
def is_order_intent(customer_text: Any, history: Any = None) -> bool:
"""当前这轮是否属于订单 / 快递话题。
直接命中关键词即为订单意图;否则只有在最近几轮里刚聊过订单、且客户这轮是
“第一个 / 9 月 18 号那个 / 单号”这类短回复时才算延续订单话题。
"""
text = str(customer_text or "").strip()
keywords = _keywords()
if matches_order_keywords(text, keywords):
return True
if not text or len(_normalize(text)) > 24:
return False
if not _FOLLOW_UP_RE.match(text):
return False
return any(matches_order_keywords(item, keywords) for item in _history_texts(history))
def filter_context(
mcp_context: dict[str, Any] | None, customer_text: Any, history: Any = None
) -> dict[str, Any]:
"""只在订单意图成立时保留会话上下文,否则返回空 dict(不挂载订单 MCP)。"""
if not mcp_context or not isinstance(mcp_context, dict):
return {}
if not is_order_intent(customer_text, history):
return {}
return dict(mcp_context)