ForcePilot/backend/package/yuxi/channel/extensions/pinduoduo/tools.py

356 lines
15 KiB
Python
Raw Normal View History

from __future__ import annotations
import logging
from yuxi.channel.extensions.pinduoduo.client import (
PddGoodsClient,
PddLogisticsClient,
PddOrderClient,
PddRefundClient,
PddSessionClient,
)
logger = logging.getLogger(__name__)
class PinduoduoTools:
def __init__(
self,
order_client: PddOrderClient,
logistics_client: PddLogisticsClient,
refund_client: PddRefundClient,
session_client: PddSessionClient | None = None,
goods_client: PddGoodsClient | None = None,
):
self._order = order_client
self._logistics = logistics_client
self._refund = refund_client
self._session = session_client
self._goods = goods_client
async def query_order(self, order_sn: str) -> str:
try:
data = await self._order.get_order_detail(order_sn)
order = data.get("order_info", data)
order_status_map = {
0: "待支付",
1: "已成团(已支付)",
2: "已发货",
3: "已签收",
4: "已取消",
5: "退款中/已退款",
}
status = order_status_map.get(order.get("order_status", -1), "未知")
return (
f"📦 订单 {order_sn}\n"
f"状态: {status}\n"
f"金额: ¥{order.get('order_amount', 0) / 100:.2f}\n"
f"商品: {order.get('goods_name', '未知商品')}\n"
f"下单时间: {order.get('created_time', '未知')}"
)
except Exception as e:
logger.exception("Query order failed: %s", order_sn)
return f"查询订单 {order_sn} 失败: {e}"
async def query_logistics(self, order_sn: str) -> str:
try:
data = await self._logistics.get_trace(order_sn)
traces = data.get("trace_list", data.get("traces", []))
if not traces:
return f"📦 订单 {order_sn} 暂无物流信息"
result = f"📦 订单 {order_sn} 物流轨迹:\n"
for t in traces[:5]:
result += f" [{t.get('time', '')}] {t.get('desc', t.get('status', ''))}\n"
return result.strip()
except Exception as e:
logger.exception("Query logistics failed: %s", order_sn)
return f"查询物流 {order_sn} 失败: {e}"
async def query_refund_status(self, order_sn: str) -> str:
try:
data = await self._refund.get_refund_info(order_sn)
refund_status_map = {
0: "买家申请退款",
1: "商家处理中",
2: "退款成功",
3: "退款关闭",
4: "商家拒绝",
}
status = refund_status_map.get(data.get("refund_status", -1), "未知")
return (
f"🔙 订单 {order_sn} 退款状态:\n"
f"状态: {status}\n"
f"退款金额: ¥{data.get('refund_amount', 0) / 100:.2f}\n"
f"申请原因: {data.get('refund_reason', '未知')}"
)
except Exception as e:
logger.exception("Query refund failed: %s", order_sn)
return f"查询退款 {order_sn} 失败: {e}"
async def transfer_human(self, session_id: str, mall_id: str, buyer_id: str, reason: str = "") -> str:
if not self._session:
return "转人工功能未初始化"
try:
await self._session.transfer_human(session_id=session_id, mall_id=mall_id, buyer_id=buyer_id, reason=reason)
return f"已成功将客服会话 {session_id} 转接人工客服。{reason and f'转接原因: {reason}' or ''}"
except Exception as e:
logger.exception("Transfer to human failed: %s", session_id)
return f"转接人工失败: {e}"
async def close_session(self, session_id: str, mall_id: str) -> str:
if not self._session:
return "关闭会话功能未初始化"
try:
await self._session.close_session(session_id=session_id, mall_id=mall_id)
return f"已成功关闭客服会话 {session_id}"
except Exception as e:
logger.exception("Close session failed: %s", session_id)
return f"关闭会话失败: {e}"
async def query_goods(self, goods_id: int) -> str:
if not self._goods:
return "商品查询功能未初始化"
try:
data = await self._goods.get_goods_detail(goods_id)
goods = data.get("goods_detail", data)
return (
f"🛒 商品 #{goods_id}\n"
f"名称: {goods.get('goods_name', '未知')}\n"
f"价格: ¥{goods.get('min_group_price', 0) / 100:.2f}\n"
f"库存: {goods.get('quantity', '未知')}\n"
f"状态: {'在售' if goods.get('is_onsale') else '下架'}"
)
except Exception as e:
logger.exception("Query goods failed: %s", goods_id)
return f"查询商品 {goods_id} 失败: {e}"
async def search_goods(self, keyword: str = "", page: int = 1) -> str:
if not self._goods:
return "商品搜索功能未初始化"
try:
data = await self._goods.get_goods_list(page=page, page_size=10)
goods_list = data.get("goods_list", [])
if not goods_list:
return f"未搜索到关键词 '{keyword}' 相关的商品"
result = f"搜索 '{keyword}':\n"
for g in goods_list[:5]:
result += (
f" #{g.get('goods_id')} {g.get('goods_name', '未知')} "
f"¥{g.get('min_group_price', 0) / 100:.2f}\n"
)
return result.strip()
except Exception as e:
logger.exception("Search goods failed: %s", keyword)
return f"搜索商品 '{keyword}' 失败: {e}"
async def agree_refund(self, order_sn: str, remark: str = "") -> str:
if not self._refund:
return "退款操作功能未初始化"
try:
await self._refund.agree_refund(order_sn, remark)
return f"✅ 已同意退款订单 {order_sn}{remark}"
except Exception as e:
logger.exception("Agree refund failed: %s", order_sn)
return f"同意退款 {order_sn} 失败: {e}"
async def reject_refund(self, order_sn: str, reason: str, remark: str = "") -> str:
if not self._refund:
return "退款操作功能未初始化"
try:
await self._refund.reject_refund(order_sn, reason, remark)
return f"❌ 已拒绝退款订单 {order_sn}。原因: {reason}"
except Exception as e:
logger.exception("Reject refund failed: %s", order_sn)
return f"拒绝退款 {order_sn} 失败: {e}"
async def refund_operate(self, order_sn: str, action: str, reason: str = "", remark: str = "") -> str:
if action == "agree":
return await self.agree_refund(order_sn, remark)
elif action == "reject":
if not reason:
return "拒绝退款必须提供原因(reason 参数不能为空)"
return await self.reject_refund(order_sn, reason, remark)
else:
return f"未知操作 '{action}',仅支持 agree/reject"
def get_agent_tools(self) -> list[dict]:
tools = [
{
"name": "pdd_query_order",
"description": "查询拼多多订单详情。参数 order_sn: 订单编号",
"parameters": {
"type": "object",
"properties": {
"order_sn": {
"type": "string",
"description": "订单编号",
}
},
"required": ["order_sn"],
},
"handler": self.query_order,
},
{
"name": "pdd_query_logistics",
"description": "查询拼多多物流轨迹。参数 order_sn: 订单编号",
"parameters": {
"type": "object",
"properties": {
"order_sn": {
"type": "string",
"description": "订单编号",
}
},
"required": ["order_sn"],
},
"handler": self.query_logistics,
},
{
"name": "pdd_query_refund",
"description": "查询拼多多退款状态。参数 order_sn: 订单编号",
"parameters": {
"type": "object",
"properties": {
"order_sn": {
"type": "string",
"description": "订单编号",
}
},
"required": ["order_sn"],
},
"handler": self.query_refund_status,
},
{
"name": "pdd_refund_operate",
"description": "处理退款申请:同意或拒绝退款。参数 order_sn: 订单编号, action: 'agree'(同意)或'reject'(拒绝), reason: 拒绝原因(action='reject'时必需), remark: 备注(可选)",
"parameters": {
"type": "object",
"properties": {
"order_sn": {
"type": "string",
"description": "订单编号",
},
"action": {
"type": "string",
"enum": ["agree", "reject"],
"description": "操作类型agree=同意退款reject=拒绝退款",
},
"reason": {
"type": "string",
"description": "拒绝原因action='reject'时必填)",
},
"remark": {
"type": "string",
"description": "备注说明(可选)",
},
},
"required": ["order_sn", "action"],
},
"handler": self.refund_operate,
},
]
if self._goods:
tools.extend([
{
"name": "pdd_query_goods",
"description": "查询拼多多商品详情。参数 goods_id: 商品数字ID",
"parameters": {
"type": "object",
"properties": {
"goods_id": {
"type": "integer",
"description": "商品数字ID",
}
},
"required": ["goods_id"],
},
"handler": self.query_goods,
},
{
"name": "pdd_search_goods",
"description": "搜索店铺商品列表。参数 keyword: 搜索关键词",
"parameters": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "搜索关键词",
}
},
"required": ["keyword"],
},
"handler": self.search_goods,
},
])
if self._session:
tools.extend([
{
"name": "pdd_transfer_human",
"description": "将当前客服会话转接人工。当 AI 无法解决问题、买家明确要求转人工、或涉及投诉/纠纷/法律风险时使用。参数 reason: 转接原因(可选)",
"parameters": {
"type": "object",
"properties": {
"reason": {
"type": "string",
"description": "转接人工的原因说明",
}
},
"required": [],
},
"handler": self.transfer_human,
},
{
"name": "pdd_close_session",
"description": "关闭当前客服会话。当买家问题已解决、对话可结束时使用。",
"parameters": {
"type": "object",
"properties": {},
"required": [],
},
"handler": self.close_session,
},
])
return tools
async def execute_agent_tool(self, tool_name: str, params: dict, context: dict) -> dict:
tool_map = {
"pdd_query_order": self.query_order,
"pdd_query_logistics": self.query_logistics,
"pdd_query_refund": self.query_refund_status,
"pdd_refund_operate": self.refund_operate,
}
if self._goods:
tool_map["pdd_query_goods"] = self.query_goods
tool_map["pdd_search_goods"] = self.search_goods
if self._session:
tool_map["pdd_transfer_human"] = self.transfer_human
tool_map["pdd_close_session"] = self.close_session
handler = tool_map.get(tool_name)
if handler is None:
return {"success": False, "error": f"Unknown tool: {tool_name}"}
try:
if tool_name in ("pdd_transfer_human", "pdd_close_session"):
session_id = context.get("session_id", "")
mall_id = context.get("mall_id", "")
buyer_id = context.get("buyer_id", "")
if not session_id or not mall_id:
return {"success": False, "error": "会话上下文信息缺失,无法执行操作"}
if tool_name == "pdd_transfer_human":
result = await self.transfer_human(
session_id=session_id, mall_id=mall_id, buyer_id=buyer_id,
reason=params.get("reason", ""),
)
else:
result = await self.close_session(session_id=session_id, mall_id=mall_id)
else:
result = await handler(**params)
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}