落地陆运私聊文字询价:选项核对后再建单查价,并按线路拉产品进群。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 16:23:30 +08:00
co-authored by Cursor
parent 83b907c4b8
commit be95d823a2
52 changed files with 4534 additions and 76 deletions
+6 -1
View File
@@ -13,17 +13,22 @@ from typing import Any
from agent.channel.queue import get_message_store
from agent.policy import inquiry_copy as copy
from agent.policy.air_text_flow import get_air_text_flow
from agent.policy.land_text_flow import get_land_text_flow
from agent.policy.sea_text_flow import get_sea_text_flow
logger = logging.getLogger(__name__)
def _pick_card_flow(sender_id: str, payload: dict | None):
"""点卡:海运书签能找回工单就走海运,否则空运。"""
"""点卡:海运/陆运书签能找回工单就走对应流程,否则空运。"""
sea = get_sea_text_flow()
sess = sea.session_for_action(sender_id, payload)
if sess and (sess.business_line or "").upper() == "SEA":
return sea
land = get_land_text_flow()
land_sess = land.session_for_action(sender_id, payload)
if land_sess and (land_sess.business_line or "").upper() == "LAND":
return land
return get_air_text_flow()
@@ -14,6 +14,7 @@ from agent.channel.queue import get_message_store
from agent.channel.wecom.models import InboundMessage
from agent.policy import inquiry_copy as copy
from agent.policy.air_text_flow import get_air_text_flow
from agent.policy.land_text_flow import get_land_text_flow
from agent.policy.sea_text_flow import get_sea_text_flow
logger = logging.getLogger(__name__)
@@ -34,6 +35,10 @@ def _pick_continue_flow(*, sender_id: str, work_order_no: str, flow=None):
hit = sea.session_by_ticket(sender_id, no) if no else None
if hit and (hit.business_line or "").upper() == "SEA":
return sea
land = get_land_text_flow()
land_hit = land.session_by_ticket(sender_id, no) if no else None
if land_hit and (land_hit.business_line or "").upper() == "LAND":
return land
getter = getattr(sea.ledger, "get_for_agent", None)
if callable(getter) and no:
try:
@@ -41,8 +46,11 @@ def _pick_continue_flow(*, sender_id: str, work_order_no: str, flow=None):
except Exception:
logger.warning("continue_thread 读主账业务线失败 wo=%s", no)
view = {}
if str(view.get("business_line") or "").upper() == "SEA":
line = str(view.get("business_line") or "").upper()
if line == "SEA":
return sea
if line == "LAND":
return land
return get_air_text_flow()
@@ -81,6 +81,18 @@ def handle_group_inbound(
if ticket is None:
logger.info("group_inbound 对不上工单 chat=%s", chat_id)
return "group_ignore"
if flow is None:
view0 = ticket_view(ticket)
line = str(
view0.get("business_line")
or view0.get("businessLine")
or getattr(ticket, "business_line", "")
or ""
).upper()
if line == "LAND":
from agent.policy.land_text_flow import get_land_text_flow
engine = get_land_text_flow()
ticket = hydrate_ticket(engine, ticket)
if not _sender_allowed(ticket, message.sender_id):
logger.info("group_inbound 忽略非成员 sender=%s chat=%s", message.sender_id, chat_id)
+87 -2
View File
@@ -15,6 +15,7 @@ from agent.channel.wecom.models import InboundMessage
from agent.config import get_settings
from agent.llm.extract_text import detect_transport_mode
from agent.policy.air_text_flow import get_air_text_flow
from agent.policy.land_text_flow import get_land_text_flow
from agent.policy.sea_text_flow import get_sea_text_flow
logger = logging.getLogger(__name__)
@@ -22,20 +23,90 @@ logger = logging.getLogger(__name__)
def _pick_text_flow(*, sender_id: str, text: str, injected_mode: str = ""):
"""
按当前书签或本句运输方式选空运/海运流程。
按当前书签或本句运输方式选空运/海运/陆运流程。
已有海运书签优先;新句写了海运走海运;其余仍走空运(含补问运输方式)。
陆运核对卡上的确定优先于旧海运书签,避免「确定」被海运/空运吃掉。
"""
from agent.schema.land_options import looks_like_confirm
land = get_land_text_flow()
if looks_like_confirm(text):
restored = land.restore_confirm_session(sender_id)
if restored:
return land
retry = land.restore_tms_retry_session(sender_id)
if retry:
return land
sea = get_sea_text_flow()
sea_sess = sea.session_of(sender_id)
if sea_sess and (sea_sess.business_line or "").upper() == "SEA":
return sea
land_sess = land.session_of(sender_id)
if land_sess and (land_sess.business_line or "").upper() == "LAND":
return land
mode = (injected_mode or detect_transport_mode(text) or "").upper()
if mode == "SEA":
return sea
if mode == "LAND":
return land
return get_air_text_flow()
def is_pending_land_confirm(message: InboundMessage) -> bool:
"""
陆运核对卡已出、本句是确定查价。
测服实测「确定」两字会被 DeepSeek A 判成闲聊,必须在分发层拦住,
不能回首次问候、不能跳过建单。
"""
if not message.has_sender():
return False
from agent.schema.land_options import looks_like_confirm
if not looks_like_confirm(message.content or ""):
return False
sess = get_land_text_flow().restore_confirm_session(message.sender_id)
ok = bool(
sess
and (sess.business_line or "").upper() == "LAND"
and sess.phase == "wait_confirm"
)
if not ok:
logger.info(
"land.confirm miss sender=%s phase=%s line=%s",
message.sender_id,
getattr(sess, "phase", "-"),
getattr(sess, "business_line", "-"),
)
return ok
def is_pending_land_tms_retry(message: InboundMessage) -> bool:
"""
陆运工单已建、上次查价失败,本句是确定重查。
不能回首次问候、不能再开一张新工单。
"""
if not message.has_sender():
return False
from agent.schema.land_options import looks_like_confirm
if not looks_like_confirm(message.content or ""):
return False
sess = get_land_text_flow().restore_tms_retry_session(message.sender_id)
return bool(
sess
and (sess.business_line or "").upper() == "LAND"
and sess.phase == "tms_tech"
and (sess.work_order_no or "").strip()
)
def is_land_confirm_action(message: InboundMessage) -> bool:
"""核对卡确定建单,或查价失败后确定重查。"""
return is_pending_land_confirm(message) or is_pending_land_tms_retry(message)
def handle_text_inquiry(
message: InboundMessage,
*,
@@ -110,6 +181,20 @@ def handle_text_other(message: InboundMessage, *, store=None) -> str:
btn = engine.match_button(message.content or "", sess)
if btn:
return handle_text_inquiry(message, store=msg_store)
if is_land_confirm_action(message):
logger.info("text_other 陆运确定/重查,改走询价流程 sender=%s", message.sender_id)
return handle_text_inquiry(message, store=msg_store)
from agent.schema.land_options import looks_like_confirm
if looks_like_confirm(message.content or ""):
body = copy.LAND_CONFIRM_EXPIRED
msg_store.enqueue_outbound(
touser=message.sender_id,
content=body,
dedupe_key=f"land-confirm-expired:{message.message_id}",
)
return "land_confirm_expired"
body = copy.FIRST_CHAT
msg_store.enqueue_outbound(