""" 入站分发壳:RouteDecision.intent → 唯一 handler。 本文件职责:维护意图→可调用对象表;dispatch_inbound 只查表调用。 禁止:在本文件写补问/建单/TMS 业务;禁止一个函数处理全部卡片。 """ from __future__ import annotations import logging from typing import Callable, Optional from agent.channel.wecom.models import InboundMessage from agent.handlers import abnormal, card_action, close_deal, continue_thread, new_inquiry from agent.handlers.echo_text import handle_echo_text from agent.routing.decision import RouteDecision logger = logging.getLogger(__name__) # Handler 签名约定:只收 InboundMessage(出站/store 由 handler 自行取依赖) HandlerFn = Callable[[InboundMessage], None] def _noop(message: InboundMessage) -> None: """忽略类意图:不做事。""" logger.info("dispatch.noop sender=%s msg=%s", message.sender_id, message.message_id) def _wrap_echo(message: InboundMessage) -> None: """ 联调占位:文字意图暂走 echo(正式应换成 new_inquiry / Graph)。 框架接线用;不含字段抽取。 """ from agent.channel.queue import get_message_store from agent.config import get_settings settings = get_settings() handle_echo_text( message, get_message_store(), ytd_env=settings.ytd_env, reply_template=settings.wecom_callback_reply_text, ) def _wrap_new(message: InboundMessage) -> None: new_inquiry.handle_new_inquiry(message) def _wrap_continue(message: InboundMessage) -> None: continue_thread.handle_continue_thread(message, thread_id="") def _wrap_card(message: InboundMessage) -> None: # 框架:卡片字段尚未从 payload 解析;空壳用默认 card_action.handle_card_action( sender_id=message.sender_id, thread_id="", wait_version=0, action="unknown", payload={}, ) def _wrap_close(message: InboundMessage) -> None: close_deal.handle_close_deal( sender_id=message.sender_id, inquiry_no="", outcome="unknown", ) def _wrap_abnormal(message: InboundMessage) -> None: abnormal.handle_abnormal(sender_id=message.sender_id, inquiry_no="", reason="dispatch") # 意图 → handler(空壳或联调 echo);扩展时只改表,不改 ChannelRuntime 分支森林 HANDLER_REGISTRY: dict[str, HandlerFn] = { "echo_text": _wrap_echo, "ordinary_text_inquiry": _wrap_echo, # 联调:有回复;正式换业务 handler "ordinary_text_other": _wrap_echo, "attachment_inquiry": _wrap_new, "image_inquiry": _wrap_new, "multi_segment_transport": _wrap_new, "tms_read_only_quote": _wrap_continue, "new_inquiry": _wrap_new, "continue_thread": _wrap_continue, "card_action": _wrap_card, "close_deal": _wrap_close, "abnormal": _wrap_abnormal, "reject_no_sender": _noop, "ignore_empty": _noop, "stub_needs_model": _wrap_echo, } def resolve_handler(intent: str) -> Optional[HandlerFn]: """查表;未知意图返回 None。""" return HANDLER_REGISTRY.get(intent) def dispatch_inbound(message: InboundMessage, decision: RouteDecision) -> str: """ 按 RouteDecision 调用唯一 handler。 返回:实际调用的 intent 名,或 unknown_intent / skipped。 副作用:仅 handler 内产生;本函数不做业务。 """ if not message.has_sender() and decision.intent != "reject_no_sender": logger.warning("dispatch 拒绝:无 sender_id") HANDLER_REGISTRY["reject_no_sender"](message) return "reject_no_sender" fn = resolve_handler(decision.intent) if fn is None: logger.warning("dispatch 未知意图 intent=%s → noop", decision.intent) _noop(message) return "unknown_intent" logger.info( "dispatch intent=%s sender=%s msg=%s", decision.intent, message.sender_id, message.message_id, ) fn(message) return decision.intent