kfid路由窗口: 5类别映射企微客服链接(ROUTING_TARGETS配置化,弃用business_contacts个人名片)+ send_service_card新函数 + ContactCard支持service_url点击打开客服会话(行政→机票酒店前台/人力→共享服务/财务→报销服务台/法务→行政法务/物业→物业服务)
This commit is contained in:
@@ -35,6 +35,61 @@ from app.services.ws_manager import manager as ws_manager
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 路由目标配置(v4.0 2026-07-18:企微客服 kfid 窗口,替代个人名片)
|
||||
# =============================================================================
|
||||
# 说明:用户提供的「公共咨询统一联系窗口」清单(企微客服 kfid 链接)。
|
||||
# 优势:人员变动不影响路由(客服窗口由团队维护),卡片点击直达客服会话。
|
||||
# 原 business_contacts 个人名片方案弃用(表保留,路由逻辑不再读表)。
|
||||
#
|
||||
# 映射规则:Dify 输出的 business_category(5 类别) → 清单中最贴近的服务窗口。
|
||||
# - 行政(打印机/复印机/保洁/名片印刷/行政杂事)→ 机票酒店前台(行政前台受理)
|
||||
# - 人力资源(工牌/考勤/入离职/社保)→ 人力资源共享服务咨询
|
||||
# - 财务(报销/发票/工资/付款)→ 总部报销服务台
|
||||
# - 法务(合同/协议/盖章/律师)→ 行政法务团队
|
||||
# - 行政-物业(空调/灯/门禁卡/车位/物业维修)→ 物业服务
|
||||
# =============================================================================
|
||||
ROUTING_TARGETS: dict[str, dict[str, str]] = {
|
||||
"行政": {
|
||||
"service_name": "机票酒店前台",
|
||||
"description": "受理和咨询快递、访客、失物招领及行政办公相关事宜",
|
||||
"url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAAV00zNJGfmuA0aG1c2qCDnQ",
|
||||
},
|
||||
"人力资源": {
|
||||
"service_name": "人力资源共享服务咨询",
|
||||
"description": "咨询和处理人力资源相关问题(工牌/考勤/入离职/社保公积金等)",
|
||||
"url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAAvSRL5i5b_Xia8vCmFc2gRw",
|
||||
},
|
||||
"财务": {
|
||||
"service_name": "总部报销服务台",
|
||||
"description": "咨询和处理总部报销系统相关问题",
|
||||
"url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAAG4HC1zWJtPuALPKl2X6jcw",
|
||||
},
|
||||
"法务": {
|
||||
"service_name": "行政法务团队",
|
||||
"description": "可咨询合同、合规、劳动争议、法律案件纠纷、外部检查、其他法律问题",
|
||||
"url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAABF746pGAY5WZ0mOTP6kGKA",
|
||||
},
|
||||
"行政-物业": {
|
||||
"service_name": "物业服务",
|
||||
"description": "咨询和处理物业相关问题(空调/照明/门禁卡/车位/维修等)",
|
||||
"url": "https://work.weixin.qq.com/nl/innerkfid/ikfCtcYBwAAUtkMyOToCZqe42ZBDupVEQ",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_routing_target(business_category: str) -> Optional[dict[str, str]]:
|
||||
"""按业务类别获取路由目标窗口配置(kfid 客服链接)。
|
||||
|
||||
Args:
|
||||
business_category: Dify 输出的业务类别(行政/人力资源/财务/法务/行政-物业)
|
||||
|
||||
Returns:
|
||||
dict: {service_name, description, url};未配置返回 None
|
||||
"""
|
||||
return ROUTING_TARGETS.get(business_category)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 共享 httpx 客户端(v4.0 P1-2:修复每次调用新建连接的泄漏问题)
|
||||
# =============================================================================
|
||||
@@ -240,6 +295,194 @@ async def get_contact_by_category(db, category: str) -> Optional[BusinessContact
|
||||
# 名片三段式发送
|
||||
# =============================================================================
|
||||
|
||||
async def send_service_card(
|
||||
db,
|
||||
conversation: Conversation,
|
||||
employee_id: str,
|
||||
target: dict[str, str],
|
||||
reason: str,
|
||||
business_category: str,
|
||||
routing_confidence: float,
|
||||
) -> None:
|
||||
"""发送服务窗口卡片消息(v4.0 kfid 模式:路由文本 → contact_card → 系统提示)。
|
||||
|
||||
与 send_contact_card 的差异:
|
||||
- 数据来源:ROUTING_TARGETS 配置(kfid 客服窗口),非 business_contacts 表
|
||||
- extra_data.contact 构造为窗口信息(name=服务名, responsibility=描述, service_url=kfid链接)
|
||||
- 前端 ContactCard 检测 service_url 存在时,点击改为打开 kfid 客服会话
|
||||
|
||||
Args:
|
||||
db: 异步 DB session
|
||||
conversation: 当前会话对象
|
||||
employee_id: 员工企微 UserID
|
||||
target: get_routing_target 返回的窗口配置 {service_name, description, url}
|
||||
reason: 路由说明文本
|
||||
business_category: 业务类别
|
||||
routing_confidence: 路由置信度
|
||||
"""
|
||||
# 构造窗口名片数据(字段对齐前端 ContactCard 的 ContactInfo 接口)
|
||||
contact_data: dict[str, Any] = {
|
||||
"id": 0,
|
||||
"name": target["service_name"],
|
||||
"gender": "",
|
||||
"department": "企业微信-通讯录-员工服务",
|
||||
"position": "公共咨询服务窗口",
|
||||
"responsibility": target["description"],
|
||||
"extension": "",
|
||||
"service_area": "",
|
||||
"wecom_userid": "", # kfid 模式无个人 userid
|
||||
"avatar_url": "",
|
||||
"business_category": business_category,
|
||||
"service_url": target["url"], # 前端检测此字段 → 点击打开链接
|
||||
}
|
||||
extra_data: dict[str, Any] = {
|
||||
"contact": contact_data,
|
||||
"routing_reason": reason,
|
||||
"business_category": business_category,
|
||||
"routing_confidence": routing_confidence,
|
||||
}
|
||||
|
||||
# === 1. 路由说明文本消息 ===
|
||||
routing_text_msg = Message(
|
||||
conversation_id=conversation.id,
|
||||
sender_type="ai",
|
||||
sender_id="ai_bot",
|
||||
sender_name="Duckula(达寇拉)",
|
||||
content=reason,
|
||||
msg_type="text",
|
||||
is_read=True,
|
||||
)
|
||||
db.add(routing_text_msg)
|
||||
await db.flush()
|
||||
|
||||
await ws_manager.broadcast_to_employees([employee_id], {
|
||||
"type": "ai_reply",
|
||||
"data": {
|
||||
"message_id": str(routing_text_msg.id),
|
||||
"conversation_id": str(conversation.id),
|
||||
"sender_type": "ai",
|
||||
"sender_id": "ai_bot",
|
||||
"sender_name": "Duckula(达寇拉)",
|
||||
"content": reason,
|
||||
"msg_type": "text",
|
||||
"is_guidance": False,
|
||||
"ai_reply_count": conversation.ai_substantive_reply_count,
|
||||
"can_call_agent": conversation.ai_substantive_reply_count >= 3,
|
||||
"conversation_status": conversation.status,
|
||||
},
|
||||
})
|
||||
try:
|
||||
await ws_manager.broadcast({
|
||||
"type": "new_message",
|
||||
"data": {
|
||||
"conversation_id": str(conversation.id),
|
||||
"message_id": str(routing_text_msg.id),
|
||||
"sender_type": "ai",
|
||||
"sender_id": "ai_bot",
|
||||
"sender_name": "Duckula(达寇拉)",
|
||||
"content": reason,
|
||||
"msg_type": "text",
|
||||
},
|
||||
})
|
||||
except Exception as ws_err:
|
||||
logger.warning(f"路由文本 WS 广播给坐席失败: {ws_err}")
|
||||
|
||||
# === 2. contact_card 窗口卡片消息 ===
|
||||
service_card_msg = Message(
|
||||
conversation_id=conversation.id,
|
||||
sender_type="ai",
|
||||
sender_id="ai_bot",
|
||||
sender_name="Duckula(达寇拉)",
|
||||
content=f"为您推荐{business_category}服务窗口:{target['service_name']}",
|
||||
msg_type="contact_card",
|
||||
extra_data=extra_data,
|
||||
is_read=True,
|
||||
)
|
||||
db.add(service_card_msg)
|
||||
await db.flush()
|
||||
|
||||
await ws_manager.broadcast_to_employees([employee_id], {
|
||||
"type": "ai_reply",
|
||||
"data": {
|
||||
"message_id": str(service_card_msg.id),
|
||||
"conversation_id": str(conversation.id),
|
||||
"sender_type": "ai",
|
||||
"sender_id": "ai_bot",
|
||||
"sender_name": "Duckula(达寇拉)",
|
||||
"content": f"为您推荐{business_category}服务窗口:{target['service_name']}",
|
||||
"msg_type": "contact_card",
|
||||
"extra_data": extra_data,
|
||||
"is_guidance": False,
|
||||
"ai_reply_count": conversation.ai_substantive_reply_count,
|
||||
"can_call_agent": conversation.ai_substantive_reply_count >= 3,
|
||||
"conversation_status": conversation.status,
|
||||
},
|
||||
})
|
||||
try:
|
||||
await ws_manager.broadcast({
|
||||
"type": "new_message",
|
||||
"data": {
|
||||
"conversation_id": str(conversation.id),
|
||||
"message_id": str(service_card_msg.id),
|
||||
"sender_type": "ai",
|
||||
"sender_id": "ai_bot",
|
||||
"sender_name": "Duckula(达寇拉)",
|
||||
"content": f"为您推荐{business_category}服务窗口:{target['service_name']}",
|
||||
"msg_type": "contact_card",
|
||||
"extra_data": extra_data,
|
||||
},
|
||||
})
|
||||
except Exception as ws_err:
|
||||
logger.warning(f"窗口卡片 WS 广播给坐席失败: {ws_err}")
|
||||
|
||||
# === 3. 系统提示消息 ===
|
||||
hint_msg = Message(
|
||||
conversation_id=conversation.id,
|
||||
sender_type="system",
|
||||
sender_id="system",
|
||||
sender_name="系统",
|
||||
content="点击上方卡片可直接联系服务窗口(企微客服会话)",
|
||||
msg_type="system",
|
||||
is_read=True,
|
||||
)
|
||||
db.add(hint_msg)
|
||||
await db.flush()
|
||||
|
||||
await ws_manager.broadcast_to_employees([employee_id], {
|
||||
"type": "ai_reply",
|
||||
"data": {
|
||||
"message_id": str(hint_msg.id),
|
||||
"conversation_id": str(conversation.id),
|
||||
"sender_type": "system",
|
||||
"sender_id": "system",
|
||||
"sender_name": "系统",
|
||||
"content": "点击上方卡片可直接联系服务窗口(企微客服会话)",
|
||||
"msg_type": "system",
|
||||
"is_guidance": False,
|
||||
"ai_reply_count": conversation.ai_substantive_reply_count,
|
||||
"can_call_agent": conversation.ai_substantive_reply_count >= 3,
|
||||
"conversation_status": conversation.status,
|
||||
},
|
||||
})
|
||||
try:
|
||||
await ws_manager.broadcast({
|
||||
"type": "new_message",
|
||||
"data": {
|
||||
"conversation_id": str(conversation.id),
|
||||
"message_id": str(hint_msg.id),
|
||||
"sender_type": "system",
|
||||
"sender_id": "system",
|
||||
"sender_name": "系统",
|
||||
"content": "点击上方卡片可直接联系服务窗口(企微客服会话)",
|
||||
"msg_type": "system",
|
||||
},
|
||||
})
|
||||
except Exception as ws_err:
|
||||
logger.warning(f"系统提示 WS 广播给坐席失败: {ws_err}")
|
||||
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def send_contact_card(
|
||||
db,
|
||||
conversation: Conversation,
|
||||
|
||||
Reference in New Issue
Block a user