新增淘宝(Taobao)渠道扩展,支持在 Yuxi 平台中集成淘宝电商客服渠道。 包含以下功能模块: - client: 淘宝 API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - message: 消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - crypto: 加解密处理 - status: 会话状态管理 - tools: Agent 工具集成 - types: 类型定义
258 lines
11 KiB
Python
258 lines
11 KiB
Python
from __future__ import annotations
|
||
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
TRADE_STATUS_MAP = {
|
||
"WAIT_BUYER_PAY": "等待买家付款",
|
||
"WAIT_SELLER_SEND_GOODS": "等待卖家发货",
|
||
"WAIT_BUYER_CONFIRM_GOODS": "等待买家确认收货",
|
||
"TRADE_FINISHED": "交易完成",
|
||
"TRADE_CLOSED": "交易关闭",
|
||
"TRADE_CLOSED_BY_TAOBAO": "交易被淘宝关闭",
|
||
}
|
||
|
||
|
||
class TaobaoTools:
|
||
def __init__(self, client):
|
||
self._client = client
|
||
|
||
async def query_order(self, order_id: str, session: str | None = None) -> str:
|
||
try:
|
||
data = await self._client.get_trade_fullinfo(int(order_id), session)
|
||
trade = data.get("trade_fullinfo_get_response", {}).get("trade", {})
|
||
|
||
status = TRADE_STATUS_MAP.get(trade.get("status", ""), trade.get("status", "未知"))
|
||
payment = trade.get("payment", "未知")
|
||
buyer_nick = trade.get("buyer_nick", "")
|
||
orders = trade.get("orders", {}).get("order", [])
|
||
|
||
lines = [
|
||
f"📦 订单 {order_id}",
|
||
f"状态: {status}",
|
||
f"金额: ¥{payment}",
|
||
f"买家: {buyer_nick}",
|
||
]
|
||
if orders:
|
||
lines.append("商品:")
|
||
for o in orders if isinstance(orders, list) else [orders]:
|
||
lines.append(f" - {o.get('title', '未知')} ×{o.get('num', 1)} ¥{o.get('price', 0)}")
|
||
return "\n".join(lines)
|
||
except Exception as e:
|
||
logger.exception("Query order failed: %s", order_id)
|
||
return f"查询订单 {order_id} 失败: {e}"
|
||
|
||
async def query_logistics(self, order_id: str, session: str | None = None) -> str:
|
||
try:
|
||
data = await self._client.get_logistics_trace(int(order_id), session)
|
||
resp = data.get("logistics_trace_search_response", {})
|
||
traces = resp.get("trace_list", resp.get("traces", []))
|
||
|
||
if not traces:
|
||
return f"📦 订单 {order_id} 暂无物流信息"
|
||
|
||
lines = [f"📦 订单 {order_id} 物流轨迹:"]
|
||
for t in (traces if isinstance(traces, list) else [traces])[:10]:
|
||
lines.append(f" [{t.get('time', '')}] {t.get('status_desc', t.get('desc', ''))}")
|
||
return "\n".join(lines)
|
||
except Exception as e:
|
||
logger.exception("Query logistics failed: %s", order_id)
|
||
return f"查询物流 {order_id} 失败: {e}"
|
||
|
||
async def query_item(self, item_id: str, session: str | None = None) -> str:
|
||
try:
|
||
data = await self._client.get_item(int(item_id), session)
|
||
item = data.get("item_get_response", {}).get("item", {})
|
||
|
||
title = item.get("title", "未知商品")
|
||
price = item.get("price", "")
|
||
desc = item.get("desc", "")[:200]
|
||
|
||
lines = [f"🛒 {title}"]
|
||
if price:
|
||
lines.append(f"价格: ¥{price}")
|
||
if desc:
|
||
lines.append(f"描述: {desc}")
|
||
return "\n".join(lines)
|
||
except Exception as e:
|
||
logger.exception("Query item failed: %s", item_id)
|
||
return f"查询商品 {item_id} 失败: {e}"
|
||
|
||
async def query_refund(self, refund_id: str, session: str | None = None) -> str:
|
||
try:
|
||
data = await self._client.qianniu_refund_get(refund_id, session)
|
||
refund = data.get("qianniu_refund_get_response", {})
|
||
|
||
status = refund.get("status", "未知")
|
||
amount = refund.get("refund_amount", refund.get("amount", "未知"))
|
||
reason = refund.get("refund_reason", refund.get("reason", "未知"))
|
||
|
||
return f"🔙 退款 {refund_id}\n状态: {status}\n退款金额: ¥{amount}\n申请原因: {reason}"
|
||
except Exception as e:
|
||
logger.exception("Query refund failed: %s", refund_id)
|
||
return f"查询退款 {refund_id} 失败: {e}"
|
||
|
||
async def query_buyer_tags(self, buyer_nick: str, session: str | None = None) -> str:
|
||
try:
|
||
data = await self._client.qianniu_buyer_tag_get(buyer_nick, session)
|
||
tags = data.get("qianniu_buyer_tag_get_response", {}).get("tags", [])
|
||
|
||
if not tags:
|
||
return f"买家 {buyer_nick} 暂无标签"
|
||
|
||
tag_names = [t.get("tag_name", str(t)) for t in tags] if isinstance(tags, list) else [str(tags)]
|
||
return f"买家 {buyer_nick} 标签: {', '.join(tag_names)}"
|
||
except Exception as e:
|
||
logger.exception("Query buyer tags failed: %s", buyer_nick)
|
||
return f"查询买家标签失败: {e}"
|
||
|
||
async def transfer_human(self, buyer_nick: str, reason: str = "", session: str | None = None) -> str:
|
||
try:
|
||
if not hasattr(self._client, "qianniu_cloudkefu_forward"):
|
||
return "转人工功能暂不可用(千牛客服转发 API 未接入)"
|
||
|
||
await self._client.qianniu_cloudkefu_forward(
|
||
buyer_nick=buyer_nick,
|
||
to_nick="", # 不指定分配到哪个客服,由分流系统自动分配
|
||
session=session,
|
||
)
|
||
return f"已将买家 {buyer_nick} 转接人工客服" + (f",原因: {reason}" if reason else "")
|
||
except Exception as e:
|
||
logger.exception("Transfer to human failed: %s", buyer_nick)
|
||
return f"转人工失败: {e}"
|
||
|
||
async def query_chat_history(self, buyer_nick: str, session: str | None = None) -> str:
|
||
end = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
begin = (datetime.now() - timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S")
|
||
try:
|
||
data = await self._client.get_chatlogs(buyer_nick, begin, end, session)
|
||
logs_data = data.get("openim_chatlogs_get_response", {}).get("chatlogs", {})
|
||
logs = logs_data.get("chatlog", [])
|
||
|
||
if not logs:
|
||
return "无近24小时聊天记录"
|
||
|
||
lines = ["近24小时聊天记录:"]
|
||
for log in (logs if isinstance(logs, list) else [logs])[:10]:
|
||
role = "买家" if log.get("role") == "buyer" else "客服"
|
||
msg = str(log.get("message", ""))[:100]
|
||
lines.append(f" [{log.get('time', '')}] {role}: {msg}")
|
||
return "\n".join(lines)
|
||
except Exception as e:
|
||
logger.exception("Query chat history failed: %s", buyer_nick)
|
||
return f"查询聊天记录失败: {e}"
|
||
|
||
def get_agent_tools(self) -> list[dict]:
|
||
return [
|
||
{
|
||
"name": "taobao_query_order",
|
||
"description": "查询淘宝订单详情,获取订单状态、金额、买家、商品列表等信息",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"order_id": {"type": "string", "description": "淘宝订单号(tid)"},
|
||
},
|
||
"required": ["order_id"],
|
||
},
|
||
"handler": self.query_order,
|
||
},
|
||
{
|
||
"name": "taobao_query_logistics",
|
||
"description": "查询淘宝订单物流轨迹",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"order_id": {"type": "string", "description": "淘宝订单号(tid)"},
|
||
},
|
||
"required": ["order_id"],
|
||
},
|
||
"handler": self.query_logistics,
|
||
},
|
||
{
|
||
"name": "taobao_query_item",
|
||
"description": "查询淘宝商品详情,包括标题、价格、描述等",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"item_id": {"type": "string", "description": "淘宝商品ID(num_iid)"},
|
||
},
|
||
"required": ["item_id"],
|
||
},
|
||
"handler": self.query_item,
|
||
},
|
||
{
|
||
"name": "taobao_query_refund",
|
||
"description": "查询淘宝退款/售后状态和详情",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"refund_id": {"type": "string", "description": "退款单号(refund_id)"},
|
||
},
|
||
"required": ["refund_id"],
|
||
},
|
||
"handler": self.query_refund,
|
||
},
|
||
{
|
||
"name": "taobao_query_buyer_tags",
|
||
"description": "查询买家标签(VIP/黑名单等)",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"buyer_nick": {"type": "string", "description": "买家淘宝昵称"},
|
||
},
|
||
"required": ["buyer_nick"],
|
||
},
|
||
"handler": self.query_buyer_tags,
|
||
},
|
||
{
|
||
"name": "taobao_transfer_human",
|
||
"description": "将当前会话转接人工客服",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"buyer_nick": {"type": "string", "description": "买家淘宝昵称"},
|
||
"reason": {
|
||
"type": "string",
|
||
"description": "转接原因(可选)",
|
||
},
|
||
},
|
||
"required": ["buyer_nick"],
|
||
},
|
||
"handler": self.transfer_human,
|
||
},
|
||
{
|
||
"name": "taobao_query_chat_history",
|
||
"description": "查询与指定买家的近24小时聊天记录",
|
||
"parameters": {
|
||
"type": "object",
|
||
"properties": {
|
||
"buyer_nick": {"type": "string", "description": "买家淘宝昵称"},
|
||
},
|
||
"required": ["buyer_nick"],
|
||
},
|
||
"handler": self.query_chat_history,
|
||
},
|
||
]
|
||
|
||
async def execute_agent_tool(self, tool_name: str, params: dict, context: dict) -> dict:
|
||
tool_map = {
|
||
"taobao_query_order": self.query_order,
|
||
"taobao_query_logistics": self.query_logistics,
|
||
"taobao_query_item": self.query_item,
|
||
"taobao_query_refund": self.query_refund,
|
||
"taobao_query_buyer_tags": self.query_buyer_tags,
|
||
"taobao_transfer_human": self.transfer_human,
|
||
"taobao_query_chat_history": self.query_chat_history,
|
||
}
|
||
handler = tool_map.get(tool_name)
|
||
if handler is None:
|
||
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
||
try:
|
||
session = context.get("session") or context.get("access_token")
|
||
result = await handler(**params, session=session)
|
||
return {"success": True, "result": result}
|
||
except Exception as e:
|
||
return {"success": False, "error": str(e)}
|