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