Files
xianjing-ai/engine/app/intent.py
T

117 lines
4.1 KiB
Python
Raw Normal View History

from __future__ import annotations
import re
# 寒暄:不写槽、先回问候再追问
_GREETING_RE = re.compile(
r"^\s*(hi|hello|hey|hola|yo|你好|您好|哈喽|嗨|早上好|上午好|中午好|下午好|晚上好|早|在吗|在不在)[\s!!.。~~??]*$",
re.I,
)
# 跳过/暂无类有效回答
_SKIP_RE = re.compile(r"暂无|没有|无|不清楚|不确定|随便|都可以|未知|n/?a|none|no\b", re.I)
# 鲜境业务相关词:冷链温控 / 官网产品线
_DOMAIN_RE = re.compile(
r"冷链|冷库|仓储|仓库|运输|配送|保温|冷冻|冷藏|温控|温度|湿度|"
r"药品|疫苗|生鲜|实验室|产线|户外|监测|告警|报警|"
r"TagPlus|BlueTag|GAEA|Fresh\s*Tag|USB|PDF|蓝牙|网关|记录仪|"
r"不开箱|行程|温区|WiFi|4G|LAN|API|方案|产品|报价|选型|"
r"实时|读数|报告|传感器|物联网|物联|预算|网络|对接|系统|次数|可重复",
re.I,
)
# 明显跑题话题
_OFFTOPIC_RE = re.compile(
r"天气|气温|下雨|新闻|股票|基金|彩票|足球|篮球|电影|电视剧|游戏|"
r"笑话|段子|星座|运势|做饭|菜谱|旅游|机票|酒店|恋爱|八卦|"
r"政治|选举|战争|加密货币|比特币|chatgpt|写诗|作文|数学题|"
r"编程作业|leetcode|今天吃什么",
re.I,
)
_QUESTION_RE = re.compile(r"[??]|什么是|怎么|如何|能否|可以吗|多少|哪[个些]|是否|介绍一下|有没有")
def is_greeting(text: str) -> bool:
return bool(_GREETING_RE.match((text or "").strip()))
def is_skip_answer(text: str) -> bool:
t = (text or "").strip()
if not t:
return False
if _SKIP_RE.fullmatch(t):
return True
return t in {"没有要求", "无要求", "没什么", "先不确定"}
def is_domain_related(text: str) -> bool:
return bool(_DOMAIN_RE.search(text or ""))
def looks_like_question(text: str) -> bool:
return bool(_QUESTION_RE.search(text or ""))
def has_offtopic_topic(text: str) -> bool:
return bool(_OFFTOPIC_RE.search(text or ""))
def looks_off_topic(text: str) -> bool:
"""明显与冷链方案无关的输入;寒暄单独处理,不算跑题。"""
t = (text or "").strip()
if not t or is_greeting(t) or is_skip_answer(t):
return False
if is_domain_related(t):
return False
if _OFFTOPIC_RE.search(t):
return True
# 纯英文闲聊 / 极短无意义且不像在答槽
if re.fullmatch(r"[a-zA-Z\s!.?~]{1,40}", t) and not is_domain_related(t):
lower = t.lower()
if any(w in lower for w in ("hello", "hi", "hey", "thanks", "bye", "ok", "test")):
return False # greeting/ack 另判
if looks_like_question(t):
return True
# 较长文本却无任何业务词 → 视为跑题
if len(t) >= 8 and not is_domain_related(t) and looks_like_question(t):
return True
if len(t) >= 16 and not is_domain_related(t):
return True
return False
def should_write_slot(text: str) -> bool:
"""是否允许本轮用户话写入槽位。"""
t = (text or "").strip()
if not t or is_greeting(t):
return False
if looks_off_topic(t):
return False
return True
def greeting_reply(next_prompt: str | None) -> str:
base = "你好!我是鲜境方案客服,可以帮您梳理冷链温控场景并匹配官网产品组合。"
if next_prompt:
return f"{base}\n\n{next_prompt}"
return f"{base}\n\n您可以直接描述场景,例如:「冷库仓储,要实时告警」。"
def refuse_reply(reason: str = "off_topic", next_prompt: str | None = None) -> str:
if reason == "kb_miss":
msg = (
"抱歉,我无法回答这个问题。"
"当前知识库里没有足够相关依据,请换一个与鲜境冷链温控产品/场景相关的问题,"
"或描述您的应用现场,我再帮您追问选型。"
)
else:
msg = (
"抱歉,我无法回答这类问题。"
"我主要协助鲜境物联的冷链温控方案咨询(运输记录、蓝牙读数、仓储网关等)。"
)
if next_prompt:
return f"{msg}\n\n我们继续:{next_prompt}"
return msg