feat: add mysql-backed customer risk tools and manual do-not-serve gate
This commit is contained in:
@@ -28,6 +28,7 @@ load_dotenv()
|
||||
from services.service_tuhui_upload import upload_to_tuhui
|
||||
from core.workflow_router import get_workflow_router
|
||||
from core.workflow_router import get_workflow_router
|
||||
from db.customer_risk_db import risk_db
|
||||
|
||||
# ========== 企业微信通知 ==========
|
||||
_WECHAT_WEBHOOK = os.getenv("WECHAT_WEBHOOK", "")
|
||||
@@ -673,6 +674,69 @@ class CustomerServiceAgent:
|
||||
"""
|
||||
return "TRANSFER_REQUESTED"
|
||||
|
||||
@self.agent.tool
|
||||
async def get_customer_risk_profile(ctx: RunContext[AgentDeps], customer_id: str = "") -> str:
|
||||
"""查询客户风控画像:退款/不付款/差评/人工黑名单等。"""
|
||||
cid = customer_id or ctx.deps.from_id
|
||||
try:
|
||||
info = risk_db.evaluate_customer(cid)
|
||||
return (
|
||||
f"客户:{cid}\n"
|
||||
f"不接单:{'是' if info.get('do_not_serve') else '否'}\n"
|
||||
f"风险等级:{info.get('computed_level','low')} 分数:{info.get('computed_score',0)}\n"
|
||||
f"近30天退款:{info.get('refund_30d',0)}\n"
|
||||
f"近7天未付款下单:{info.get('unpaid_7d',0)}\n"
|
||||
f"近90天差评:{info.get('bad_review_90d',0)}\n"
|
||||
f"备注:{info.get('note','') or '无'}"
|
||||
)
|
||||
except Exception as e:
|
||||
return f"查询风控画像失败: {e}"
|
||||
|
||||
@self.agent.tool
|
||||
async def mark_customer_risk(
|
||||
ctx: RunContext[AgentDeps],
|
||||
customer_id: str,
|
||||
do_not_serve: bool = False,
|
||||
risk_level: str = "low",
|
||||
risk_score: int = 0,
|
||||
note: str = "",
|
||||
tag: str = "",
|
||||
) -> str:
|
||||
"""人工标记客户风控画像(不接单/高风险/备注标签)。"""
|
||||
try:
|
||||
tags = [tag] if tag else []
|
||||
risk_db.set_profile(
|
||||
customer_id=customer_id,
|
||||
do_not_serve=do_not_serve,
|
||||
risk_level=risk_level,
|
||||
risk_score=risk_score,
|
||||
note=note,
|
||||
tags=tags,
|
||||
)
|
||||
return "风控画像已更新"
|
||||
except Exception as e:
|
||||
return f"更新风控画像失败: {e}"
|
||||
|
||||
@self.agent.tool
|
||||
async def record_customer_risk_event(
|
||||
ctx: RunContext[AgentDeps],
|
||||
customer_id: str,
|
||||
event_type: str,
|
||||
event_count: int = 1,
|
||||
note: str = "",
|
||||
) -> str:
|
||||
"""记录风控事件:refund/unpaid_order/bad_review/blacklist_hit 等。"""
|
||||
try:
|
||||
risk_db.record_event(
|
||||
customer_id=customer_id,
|
||||
event_type=event_type,
|
||||
event_count=event_count,
|
||||
note=note,
|
||||
)
|
||||
return "风控事件已记录"
|
||||
except Exception as e:
|
||||
return f"记录风控事件失败: {e}"
|
||||
|
||||
@self.agent.tool
|
||||
async def save_customer_note(
|
||||
ctx: RunContext[AgentDeps],
|
||||
@@ -842,6 +906,46 @@ class CustomerServiceAgent:
|
||||
async def risk_filter(ctx: RunContext[AgentDeps], text: str = "") -> str:
|
||||
return "这类不做哈,政治/敏感内容都不接。"
|
||||
|
||||
@self.agent_risk.tool
|
||||
async def get_customer_risk_profile_risk(ctx: RunContext[AgentDeps], customer_id: str = "") -> str:
|
||||
return await get_customer_risk_profile(ctx, customer_id)
|
||||
|
||||
@self.agent_risk.tool
|
||||
async def mark_customer_risk_risk(
|
||||
ctx: RunContext[AgentDeps],
|
||||
customer_id: str,
|
||||
do_not_serve: bool = False,
|
||||
risk_level: str = "low",
|
||||
risk_score: int = 0,
|
||||
note: str = "",
|
||||
tag: str = "",
|
||||
) -> str:
|
||||
return await mark_customer_risk(
|
||||
ctx=ctx,
|
||||
customer_id=customer_id,
|
||||
do_not_serve=do_not_serve,
|
||||
risk_level=risk_level,
|
||||
risk_score=risk_score,
|
||||
note=note,
|
||||
tag=tag,
|
||||
)
|
||||
|
||||
@self.agent_risk.tool
|
||||
async def record_customer_risk_event_risk(
|
||||
ctx: RunContext[AgentDeps],
|
||||
customer_id: str,
|
||||
event_type: str,
|
||||
event_count: int = 1,
|
||||
note: str = "",
|
||||
) -> str:
|
||||
return await record_customer_risk_event(
|
||||
ctx=ctx,
|
||||
customer_id=customer_id,
|
||||
event_type=event_type,
|
||||
event_count=event_count,
|
||||
note=note,
|
||||
)
|
||||
|
||||
@self.agent.tool
|
||||
async def remove_background(ctx: RunContext[AgentDeps], image_url: str) -> str:
|
||||
try:
|
||||
@@ -1687,6 +1791,21 @@ class CustomerServiceAgent:
|
||||
|
||||
# 前置风控:客户文本一旦命中政治/敏感询问,直接拒绝,避免“发图我看看”类答非所问
|
||||
try:
|
||||
# 人工风控:标记为不接单的客户直接转人工
|
||||
manual_risk = risk_db.evaluate_customer(message.from_id)
|
||||
if bool(manual_risk.get("do_not_serve")):
|
||||
self._activity_log(
|
||||
"agent_manual_risk_reject",
|
||||
customer_id=message.from_id,
|
||||
risk=manual_risk,
|
||||
)
|
||||
return AgentResponse(
|
||||
reply="这边无法继续为你处理该类需求,给你转人工专员对接。",
|
||||
should_reply=True,
|
||||
need_transfer=True,
|
||||
transfer_msg=TRANSFER_MESSAGE,
|
||||
)
|
||||
|
||||
from utils.content_filter import should_block_customer_smart
|
||||
risk_hit, risk_category, _risk_reason = await should_block_customer_smart(message.msg)
|
||||
map_hit = self._is_map_inquiry(message.msg) or (risk_category == "map")
|
||||
|
||||
Reference in New Issue
Block a user