ForcePilot/backend/package/yuxi/channel/extensions/taobao/tools.py
Kris 904ab9a6c2 feat(channel): 添加淘宝渠道扩展
新增淘宝(Taobao)渠道扩展,支持在 Yuxi 平台中集成淘宝电商客服渠道。

包含以下功能模块:
- client: 淘宝 API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- message: 消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- crypto: 加解密处理
- status: 会话状态管理
- tools: Agent 工具集成
- types: 类型定义
2026-05-21 11:46:45 +08:00

258 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)}