query_order_exact.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. class QueryOrderExactTool:
  2. name = 'query_order_exact'
  3. route_path = '/mcp/tools/queryOrderExact'
  4. STRING_FILTERS = (
  5. 'order_number',
  6. 'reference_number',
  7. 'tracking_number',
  8. 'outbound_number',
  9. 'container_code',
  10. 'so_number',
  11. 'shipment_id',
  12. 'receiver_country',
  13. 'inbound_date_start',
  14. 'inbound_date_end',
  15. 'outbound_date_start',
  16. 'outbound_date_end',
  17. )
  18. ARRAY_FILTERS = ('product_ids', 'customer_ids', 'warehouse_ids')
  19. NUMBER_ARRAY_FILTERS = (
  20. 'order_numbers',
  21. 'reference_numbers',
  22. 'tracking_numbers',
  23. 'outbound_numbers',
  24. 'container_codes',
  25. 'so_numbers',
  26. )
  27. NUMBER_FILTER_MAP = {
  28. 'order_numbers': 'order_number',
  29. 'reference_numbers': 'reference_number',
  30. 'tracking_numbers': 'tracking_number',
  31. 'outbound_numbers': 'outbound_number',
  32. 'container_codes': 'container_code',
  33. 'so_numbers': 'so_number',
  34. }
  35. SCALAR_ID_FILTERS = ('sales_id', 'department_id')
  36. MAX_NUMBER_FILTER_COUNT = 200
  37. FIELD_GUIDANCE = {
  38. 'order_number': (
  39. '系统订单号,用户明确说“订单号”且只有一个值时使用;'
  40. '不是客户参考号或快递单号。',
  41. ['USC26070917207'],
  42. ),
  43. 'order_numbers': (
  44. '多个系统订单号的批量精准查询;用户明确给出多个订单号时使用。',
  45. [['USC26070917207', 'USC26070917208']],
  46. ),
  47. 'reference_number': (
  48. '客户参考号,用户明确说“客户参考号”且只有一个值时使用;'
  49. '不是系统订单号。',
  50. ['REF-20260713-001'],
  51. ),
  52. 'reference_numbers': (
  53. '多个客户参考号的批量精准查询;用户明确给出多个客户参考号时使用。',
  54. [['REF-001', 'REF-002']],
  55. ),
  56. 'tracking_number': (
  57. '快递单号、物流跟踪号或承运商跟踪号码;用户明确说“快递单号”、'
  58. '“物流跟踪号”或“承运商跟踪号”且只有一个值时使用;'
  59. '不是系统订单号。',
  60. ['1Z999AA10123456784'],
  61. ),
  62. 'tracking_numbers': (
  63. '多个快递单号或承运商跟踪号码的批量精准查询;'
  64. '用户明确给出多个此类号码时使用。',
  65. [['TRACK-001', 'TRACK-002']],
  66. ),
  67. 'outbound_number': (
  68. '排舱单号,用户明确说“排舱单号”且只有一个值时使用;'
  69. '不是海外仓出库单号。',
  70. ['PC20210331070002001'],
  71. ),
  72. 'outbound_numbers': (
  73. '多个排舱单号的批量精准查询;用户明确给出多个排舱单号时使用。',
  74. [['PC20210331070002001', 'PC20210331070002002']],
  75. ),
  76. 'container_code': (
  77. '柜号或集装箱号,用户明确说“柜号”或“集装箱号”且只有一个值时使用。',
  78. ['MSCU1234567'],
  79. ),
  80. 'container_codes': (
  81. '多个柜号或集装箱号的批量精准查询;用户明确给出多个此类号码时使用。',
  82. [['MSCU1234567', 'TGHU7654321']],
  83. ),
  84. 'so_number': (
  85. 'SO号,即 Shipping Order 编号,格式不固定;'
  86. '用户明确说“SO号”且只有一个值时使用。',
  87. ['97964454'],
  88. ),
  89. 'so_numbers': (
  90. '多个格式不固定的 SO 号或 Shipping Order 编号的批量精准查询;'
  91. '用户明确给出多个 SO 号时使用。',
  92. [['97964454', 'OOLU12345678']],
  93. ),
  94. 'shipment_id': (
  95. 'Shipment ID,用户明确说“Shipment ID”且只有一个值时使用。',
  96. ['FBA123456789'],
  97. ),
  98. 'receiver_country': (
  99. '收货国家代码;先调用 list_order_filter_options 的 country 类型'
  100. '取得可用值。',
  101. ['US'],
  102. ),
  103. 'product_ids': (
  104. '物流产品 ID 数组;必须先调用 list_order_filter_options 的 '
  105. 'product 类型取得 ID。',
  106. [[101, 102]],
  107. ),
  108. 'customer_ids': (
  109. '客户 ID 数组;必须先调用 list_order_filter_options 的 customer '
  110. '类型取得 ID。',
  111. [[201, 202]],
  112. ),
  113. 'sales_id': (
  114. '销售人员 ID;必须先调用 list_order_filter_options 的 sales 类型'
  115. '取得 ID。',
  116. [301],
  117. ),
  118. 'warehouse_ids': (
  119. '仓库 ID 数组;必须先调用 list_order_filter_options 的 warehouse '
  120. '类型取得 ID,-1 表示客户仓。',
  121. [[401, -1]],
  122. ),
  123. 'department_id': (
  124. '事业部 ID;必须先调用 list_order_filter_options 的 department '
  125. '类型取得 ID。',
  126. [501],
  127. ),
  128. 'inbound_date_start': (
  129. '入库日期范围开始日期,格式 YYYY-MM-DD。',
  130. ['2026-07-01'],
  131. ),
  132. 'inbound_date_end': (
  133. '入库日期范围结束日期,格式 YYYY-MM-DD。',
  134. ['2026-07-13'],
  135. ),
  136. 'outbound_date_start': (
  137. '出库日期范围开始日期,格式 YYYY-MM-DD。',
  138. ['2026-07-01'],
  139. ),
  140. 'outbound_date_end': (
  141. '出库日期范围结束日期,格式 YYYY-MM-DD。',
  142. ['2026-07-13'],
  143. ),
  144. 'page': ('结果页码,从 1 开始。', [1]),
  145. 'limit': ('每页记录数,范围 1 到 100。', [20]),
  146. }
  147. def __init__(self, api_client=None):
  148. self.api_client = api_client
  149. def metadata(self):
  150. properties = {
  151. field: {'type': 'string'}
  152. for field in self.STRING_FILTERS
  153. }
  154. for field in self.ARRAY_FILTERS:
  155. properties[field] = {
  156. 'type': 'array',
  157. 'items': {'type': 'integer'},
  158. }
  159. for field in self.NUMBER_ARRAY_FILTERS:
  160. properties[field] = {
  161. 'type': 'array',
  162. 'items': {'type': 'string'},
  163. }
  164. for field in self.SCALAR_ID_FILTERS:
  165. properties[field] = {'type': 'integer', 'minimum': 1}
  166. properties.update({
  167. 'page': {'type': 'integer', 'minimum': 1, 'maximum': 100},
  168. 'limit': {'type': 'integer', 'minimum': 1, 'maximum': 100},
  169. })
  170. for field, guidance in self.FIELD_GUIDANCE.items():
  171. properties[field]['description'] = guidance[0]
  172. properties[field]['examples'] = guidance[1]
  173. return {
  174. 'name': self.name,
  175. 'description': (
  176. '使用场景:用户要查看订单列表或按条件批量精准筛选订单,并且已经明确每个号码的'
  177. '业务类型时,按指定字段精准查询订单;即使使用排舱单号、柜号或SO号筛选,'
  178. '返回目标仍是订单资料。'
  179. '本工具只返回订单列表结果;用户要求查看单个订单的详情、概览、箱单、商品、'
  180. '轨迹、日志或其他详情模块时,必须改用 query_order_detail,不得使用本工具替代。'
  181. '订单号、客户参考号、快递单号、排舱单号、柜号和 SO 号必须'
  182. '使用各自对应字段。'
  183. '所有单号格式均为开放格式,只能根据用户明确说出的业务类型'
  184. '选择字段,不得根据号码的前缀、长度、字符组合或示例猜测。'
  185. '不得根据号码格式猜测。'
  186. '号码类型不明确时必须先询问用户;单号类型不明确时先询问用户,'
  187. '确认前不得调用。'
  188. '一个值使用单数字段,多个同类值使用复数字段数组;'
  189. '同一字段使用 IN,不同字段使用 AND。'
  190. '禁止使用:用户要看排舱列表、排舱详情、物流轨迹、报关资料文件或导出'
  191. '结果时不得使用本工具。不得跨字段或跨工具试查;查询无结果时不得改用'
  192. '其他字段重试。'
  193. '产品、客户、销售、仓库、事业部和国家条件先调用 '
  194. 'list_order_filter_options。'
  195. '参数名仅用于工具调用;向用户回答时只能使用对应的中文业务名称,'
  196. '不得展示内部参数名。'
  197. ),
  198. 'input_schema': {
  199. 'type': 'object',
  200. 'properties': properties,
  201. },
  202. }
  203. def call(
  204. self,
  205. order_number='',
  206. reference_number='',
  207. tracking_number='',
  208. outbound_number='',
  209. container_code='',
  210. so_number='',
  211. shipment_id='',
  212. receiver_country='',
  213. product_ids=None,
  214. customer_ids=None,
  215. sales_id=None,
  216. warehouse_ids=None,
  217. department_id=None,
  218. inbound_date_start='',
  219. inbound_date_end='',
  220. outbound_date_start='',
  221. outbound_date_end='',
  222. page=1,
  223. limit=20,
  224. request_id='rq_query_order_exact',
  225. order_numbers=None,
  226. reference_numbers=None,
  227. tracking_numbers=None,
  228. outbound_numbers=None,
  229. container_codes=None,
  230. so_numbers=None,
  231. ):
  232. if self.api_client is None:
  233. raise RuntimeError('api client is required for query_order_exact')
  234. values = locals()
  235. payload = {}
  236. for field in self.STRING_FILTERS:
  237. value = str(values[field] or '').strip()
  238. if value:
  239. payload[field] = value
  240. number_count = 0
  241. for field in self.NUMBER_ARRAY_FILTERS:
  242. value = self._normalize_string_list(values[field], field)
  243. if value:
  244. payload[field] = value
  245. singular = self.NUMBER_FILTER_MAP[field]
  246. merged = list(value)
  247. singular_value = payload.get(singular)
  248. if singular_value and singular_value not in merged:
  249. merged.insert(0, singular_value)
  250. number_count += len(merged)
  251. if number_count > self.MAX_NUMBER_FILTER_COUNT:
  252. raise ValueError('number filters must not exceed 200 items')
  253. for field in self.ARRAY_FILTERS:
  254. allow_customer_warehouse = field == 'warehouse_ids'
  255. value = self._normalize_int_list(
  256. values[field],
  257. allow_customer_warehouse=allow_customer_warehouse,
  258. )
  259. if value:
  260. payload[field] = value
  261. for field in self.SCALAR_ID_FILTERS:
  262. value = values[field]
  263. if value is None or value == '':
  264. continue
  265. value = int(value)
  266. if value <= 0:
  267. raise ValueError('{0} must be greater than 0'.format(field))
  268. payload[field] = value
  269. if not payload:
  270. raise ValueError('at least one exact order filter is required')
  271. payload['page'] = max(1, min(100, int(page)))
  272. payload['limit'] = max(1, min(100, int(limit)))
  273. return self.api_client.call_tool(
  274. self.name,
  275. self.route_path,
  276. payload,
  277. request_id,
  278. )
  279. @staticmethod
  280. def _normalize_int_list(value, allow_customer_warehouse=False):
  281. if value is None or value == '':
  282. return []
  283. if isinstance(value, str):
  284. value = [item.strip() for item in value.split(',') if item.strip()]
  285. result = []
  286. for item in value:
  287. item = int(item)
  288. if item <= 0 and not (allow_customer_warehouse and item == -1):
  289. continue
  290. if item not in result:
  291. result.append(item)
  292. return result
  293. @staticmethod
  294. def _normalize_string_list(value, field):
  295. if value is None or value == '':
  296. return []
  297. if not isinstance(value, (list, tuple)):
  298. raise ValueError('{0} must be an array'.format(field))
  299. result = []
  300. for item in value:
  301. if not isinstance(item, str):
  302. raise ValueError('{0} items must be strings'.format(field))
  303. item = item.strip()
  304. if not item or len(item) > 100:
  305. raise ValueError('{0} contains an invalid number'.format(field))
  306. if item not in result:
  307. result.append(item)
  308. return result