Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled
115 lines
4.5 KiB
Python
115 lines
4.5 KiB
Python
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
|