134 lines
4.3 KiB
Python
134 lines
4.3 KiB
Python
"""
|
||
意图路由:DeepSeek A → 唯一 RouteDecision。
|
||
|
||
禁止:在本包产生业务副作用(建单、发消息、改六态)。
|
||
正式路径:DeepSeekARouter(strict tool);route_stub 仅兼容旧自检名。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from dataclasses import dataclass
|
||
from typing import Optional
|
||
|
||
from agent.llm import LlmGateway, LlmMode
|
||
from agent.routing.decision import RouteDecision
|
||
from agent.routing.deepseek_a import DeepSeekARouter
|
||
from agent.routing.dispatch import HANDLER_REGISTRY, dispatch_inbound, resolve_handler
|
||
from agent.routing.ordered_actions import (
|
||
ORDERED_ACTIONS_TOOL_NAME,
|
||
OrderedActionsError,
|
||
OrderedActionsResult,
|
||
ordered_actions_tool_definition,
|
||
parse_ordered_actions_arguments,
|
||
)
|
||
|
||
# IntentRouter 定义见下
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@dataclass
|
||
class IntentRouter:
|
||
"""
|
||
路由门面。
|
||
|
||
- route():优先 DeepSeek A(默认 stub,不开费)
|
||
- enqueue_ordered_actions():快回路径入 LLM Stream
|
||
"""
|
||
|
||
llm: Optional[LlmGateway] = None
|
||
deepseek_a: Optional[DeepSeekARouter] = None
|
||
allow_network: bool = False
|
||
|
||
def _a(self) -> DeepSeekARouter:
|
||
if self.deepseek_a is not None:
|
||
return self.deepseek_a
|
||
return DeepSeekARouter.from_settings(allow_network=self.allow_network)
|
||
|
||
def route_stub(self, *, text: str, sender_id: str) -> RouteDecision:
|
||
"""
|
||
兼容旧名:等价于无网络的 A stub fixture。
|
||
|
||
正式生产请用 route() 并在 Worker 开 allow_network + 数据确认。
|
||
"""
|
||
return self._a().route_stub_fixture(text=text, sender_id=sender_id)
|
||
|
||
def route(self, *, text: str, sender_id: str) -> RouteDecision:
|
||
"""一条入站一个 RouteDecision。"""
|
||
return self._a().route(text=text, sender_id=sender_id)
|
||
|
||
def route_inbound(
|
||
self,
|
||
*,
|
||
text: str,
|
||
sender_id: str,
|
||
event: str = "",
|
||
) -> RouteDecision:
|
||
"""
|
||
入站后再路由。
|
||
|
||
点卡片是结构化事件,不进大模型。
|
||
自由文字交给 DeepSeek A 认意图(续办 / 新询价 / 闲聊),不要求固定口令。
|
||
"""
|
||
if not sender_id:
|
||
return RouteDecision(intent="reject_no_sender", confidence=1.0)
|
||
kind = (event or "").strip().lower()
|
||
if kind in {"template_card_event", "click", "templatecardevent"}:
|
||
return RouteDecision(intent="card_action", confidence=1.0)
|
||
from agent.handlers.text_inquiry import is_land_confirm_action
|
||
from agent.channel.wecom.models import InboundMessage
|
||
from agent.schema.land_options import looks_like_confirm
|
||
|
||
if looks_like_confirm(text or ""):
|
||
if is_land_confirm_action(
|
||
InboundMessage(
|
||
sender_id=sender_id,
|
||
content=text or "",
|
||
message_id="route-land-confirm",
|
||
)
|
||
):
|
||
logger.info("route 陆运核对卡确定:跳过闲聊模型 sender=%s", sender_id)
|
||
return RouteDecision(intent="ordinary_text_inquiry", confidence=1.0)
|
||
logger.info("route 确定类短句但无陆运核对卡,仍走模型 sender=%s", sender_id)
|
||
return self.route(text=text, sender_id=sender_id)
|
||
|
||
def enqueue_ordered_actions(
|
||
self,
|
||
*,
|
||
text: str,
|
||
sender_id: str,
|
||
idempotency_key: str,
|
||
) -> str:
|
||
"""
|
||
将 A 路由任务入 LLM 队列(快回路径用)。
|
||
|
||
返回 stream entry id;无 LlmGateway 时抛错。
|
||
"""
|
||
if self.llm is None:
|
||
raise RuntimeError("IntentRouter 未注入 LlmGateway")
|
||
result = self.llm.enqueue(
|
||
mode=LlmMode.ORDERED_ACTIONS,
|
||
payload={"text": text, "sender_id": sender_id},
|
||
idempotency_key=idempotency_key,
|
||
)
|
||
if not result.accepted:
|
||
raise RuntimeError(result.reason or "enqueue_rejected")
|
||
return result.entry_id
|
||
|
||
|
||
__all__ = [
|
||
"RouteDecision",
|
||
"IntentRouter",
|
||
"DeepSeekARouter",
|
||
"ORDERED_ACTIONS_TOOL_NAME",
|
||
"OrderedActionsError",
|
||
"OrderedActionsResult",
|
||
"ordered_actions_tool_definition",
|
||
"parse_ordered_actions_arguments",
|
||
"HANDLER_REGISTRY",
|
||
"dispatch_inbound",
|
||
"resolve_handler",
|
||
]
|