from __future__ import annotations from typing import Any from .agents import AfterSalesAgent, PreSalesAgent, QuoteAgent, RiskAgent, RouterAgent from .config import FAST_ROUTE_ENABLED from .models import Decision from .rules import ( detect_intent, detect_order_status, has_map_or_political_risk, has_porn_risk, requests_external_contact, ) from .state_machine import evolve_after_sales_state, migrate_state_schema from .store import ConversationStore class Orchestrator: def __init__(self) -> None: self.router = RouterAgent() self.pre_sales = PreSalesAgent() self.quote = QuoteAgent() self.after_sales = AfterSalesAgent() self.risk = RiskAgent() self.store = ConversationStore() async def decide(self, context: dict[str, Any]) -> tuple[str, Decision, dict[str, Any]]: customer_key = context["customer_key"] session = self.store.get_session(customer_key) prev_state = migrate_state_schema(session.get("state", {})) prev_route = session.get("route", "pre_sales") intent = detect_intent(str(context.get("msg", "") or "")) order_status = detect_order_status(str(context.get("goods_order", "") or "")) merged_ctx = { **context, "session_state": prev_state, "previous_route": prev_route, "intent": intent, "order_status": order_status, } msg = str(context.get("msg", "") or "") goods_name = str(context.get("goods_name", "") or "") risk_hit = has_map_or_political_risk(msg, goods_name) or has_porn_risk(msg) or requests_external_contact(msg) # 命中硬风控才调用 RiskAgent,避免每条消息都先走一轮模型。 if risk_hit: risk_decision = await self.risk.decide(merged_ctx) route = "risk" new_state = evolve_after_sales_state( {**prev_state, **(risk_decision.state_patch or {})}, route=route, action=risk_decision.action, intent=intent, order_status=order_status, msg=str(context.get("msg", "") or ""), ) self.store.upsert_session(customer_key, context.get("acc_id", ""), context.get("customer_id", ""), route, new_state) self.store.append_event(customer_key, "decision", {"route": route, "action": risk_decision.action, "reason": risk_decision.reason}) return route, risk_decision, new_state route = "" route_reason = "" if FAST_ROUTE_ENABLED: pending_images = int(context.get("pending_images", 0) or 0) auto_quote_trigger = bool(context.get("auto_quote_trigger", False)) if intent in {"pricing", "finish_or_quote_trigger"} and (pending_images > 0 or auto_quote_trigger): route = "quote" route_reason = "fast_route_quote_with_pending_images" elif order_status == "refund": route = "after_sales" route_reason = "fast_route_refund" elif intent in {"image", "greeting", "nonsense", "pricing", "finish_or_quote_trigger", "unknown"}: route = "pre_sales" route_reason = "fast_route_common_presales" if not route: route, route_reason = await self.router.route(merged_ctx) if route == "quote": decision = await self.quote.decide(merged_ctx) elif route == "after_sales": decision = await self.after_sales.decide(merged_ctx) elif route == "risk": decision = await self.risk.decide(merged_ctx) else: decision = await self.pre_sales.decide(merged_ctx) merged_state = {**prev_state, **(decision.state_patch or {})} new_state = evolve_after_sales_state( merged_state, route=route, action=decision.action, intent=intent, order_status=order_status, msg=str(context.get("msg", "") or ""), ) self.store.upsert_session(customer_key, context.get("acc_id", ""), context.get("customer_id", ""), route, new_state) self.store.append_event( customer_key, "decision", { "route": route, "route_reason": route_reason, "action": decision.action, "reason": decision.reason, "after_sales_stage": new_state.get("after_sales_stage", "new"), }, ) return route, decision, new_state