Files
inquiry_robot/inquiry-agent/agent/routing/air_group_intent.py
T

192 lines
6.2 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
空运协同群文字意图:激活 / 补含电含磁 / 航线报价 / 锁舱释放 / 成交。
本文件职责:只分类,不写主账、不发企微。
禁止:把「锁舱」写成唯一口令;海运解散不走这里。
"""
from __future__ import annotations
import logging
from typing import Optional
from agent.policy import inquiry_copy as copy
from agent.routing.group_intent import (
INTENT_CONFIRM_TMS,
INTENT_DEAL,
INTENT_DROP_TMS,
INTENT_FIELDS,
INTENT_KEEP_TMS,
INTENT_ADJUST,
INTENT_LOST,
INTENT_NEGOTIATE,
INTENT_OTHER,
INTENT_QUOTE,
_hint_confirm_tms,
_hint_deal_outcome,
_hint_tms_choice,
)
logger = logging.getLogger(__name__)
INTENT_ACTIVATE = "air_activate"
INTENT_LOCK = "air_lock"
INTENT_RELEASE = "air_release"
INTENT_PICK_OPTION = "air_pick_option"
_VALID = {
INTENT_ACTIVATE,
INTENT_LOCK,
INTENT_RELEASE,
INTENT_PICK_OPTION,
INTENT_FIELDS,
INTENT_QUOTE,
INTENT_ADJUST,
INTENT_KEEP_TMS,
INTENT_DROP_TMS,
INTENT_CONFIRM_TMS,
INTENT_DEAL,
INTENT_LOST,
INTENT_NEGOTIATE,
INTENT_OTHER,
}
# 相近说法回退,不是唯一口令。
_LOCK_HINTS = ("锁舱", "锁仓", "锁定舱位", "锁住舱位", "帮我锁舱")
_RELEASE_HINTS = ("释放舱位", "放舱", "解锁舱", "取消锁舱", "释放舱")
def is_missed_air_bot_at(text: str, *, room_already_air: bool = False) -> bool:
"""
存档里看到用户已经 @ 询价小助手。BOT 长连接没推到时,用这一刀补听。
海运拉人卡片也带「询价小助手」,必须排除。闲聊没有 @ 仍不当业务。
"""
raw = (text or "").strip()
if "询价小助手" not in raw:
return False
if "运输方式:海运" in raw or "运输方式:海运" in raw:
return False
wo = copy.extract_work_order_no(raw)
if wo:
compact = "".join(raw.split())
return len(compact) < 80
return bool(room_already_air)
def classify_air_group_text(
text: str,
*,
injected: str = "",
allow_network: Optional[bool] = None,
) -> str:
"""
空运群一句文字的意图。单测可注入;正式走模型。
锁舱/释放优先于成交,避免「锁舱成交」误伤。
"""
hint = (injected or "").strip()
if hint in _VALID:
return hint
raw = (text or "").strip()
if not raw:
return INTENT_OTHER
locked = _hint_lock_or_release(raw)
if locked:
return locked
# 只发工单号先当激活,不要先打模型;WO + 12 位数字也会被费用行误认成金额。
if copy.extract_work_order_no(raw) and _only_work_order(raw):
return INTENT_ACTIVATE
try:
from agent.config import get_settings
settings = get_settings()
use_net = (
allow_network
if allow_network is not None
else bool(getattr(settings, "llm_allow_network", False))
)
if use_net and getattr(settings, "llm_data_usage_confirmed", False):
got = _classify_with_model(raw, settings)
if got in _VALID:
return got
except Exception:
logger.exception("空运群意图模型失败,回退规则")
from agent.llm.extract_text import extract_air_collab_fields, extract_product_quote
from agent.schema.quote_adjust import extract_adjust
if extract_adjust(raw).is_adjust:
return INTENT_ADJUST
if extract_product_quote(raw):
return INTENT_QUOTE
confirmed = _hint_confirm_tms(raw)
if confirmed:
return confirmed
hinted = _hint_tms_choice(raw)
if hinted:
return hinted
closed = _hint_deal_outcome(raw)
if closed:
return closed
if extract_air_collab_fields(raw):
return INTENT_FIELDS
if _looks_like_option(raw):
return INTENT_PICK_OPTION
return INTENT_OTHER
def _hint_lock_or_release(text: str) -> str:
blob = (text or "").replace(" ", "").replace(" ", "")
if any(x in blob for x in _RELEASE_HINTS):
return INTENT_RELEASE
if any(x in blob for x in _LOCK_HINTS):
return INTENT_LOCK
return ""
def _only_work_order(text: str) -> bool:
"""去掉工单号和 @询价小助手 后几乎没字,当作激活/切单。"""
no = copy.extract_work_order_no(text)
rest = (text or "").replace(no, "").replace(no.lower(), "")
rest = rest.replace("@询价小助手", "").replace("询价小助手", "")
rest = "".join(ch for ch in rest if ch.isalnum() or "\u4e00" <= ch <= "\u9fff")
return not rest.strip()
def _looks_like_option(text: str) -> bool:
blob = (text or "").replace(" ", "").upper()
return blob.startswith("AIR-OPT") or blob.startswith("AIROPT")
def _classify_with_model(text: str, settings) -> str:
from agent.llm.http_client import LlmHttpClient
from agent.llm.modes import LlmMode
client = LlmHttpClient.from_settings(settings)
system = (
"你是空运协同群意图分类。只回复一个英文词,不要解释。"
"air_lock=航线要锁舱;air_release=航线要释放舱位;"
"air_activate=只是指定/切换工单号;"
"air_pick_option=在选舱位选项编号;"
"collab_fields=在补是否含电/是否含磁;"
"quote_adjust=销售在调已有费用,口令不写死(上升、往上抬、加一成、改到某金额都算),不是新产品报价;"
"product_quote=航线在报价格或费用(含具体金额);"
"quote_confirm_tms=确认沿用当前TMS报价;"
"quote_keep_tms=要在已有TMS报价上改;"
"quote_drop_tms=不要TMS、只用刚报的价;"
"deal_won=销售确认成交;deal_lost=销售确认未成交;"
"deal_negotiate=销售要继续协商;"
"other=闲聊。"
"一句话里既有锁舱又有工单号,回 air_lock。"
"改已有费用优先 quote_adjust;新报价格才是 product_quote。"
)
data = client.chat(
mode=LlmMode.ORDERED_ACTIONS,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": text[:500]},
],
)
got = str((data or {}).get("content") or "").strip().split()[0] if data else ""
return got if got in _VALID else ""