export_out_of_province_port_data.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. class ExportOutOfProvincePortDataTool:
  2. name = 'export_out_of_province_port_data'
  3. route_path = '/mcp/tools/exportOutOfProvincePortData'
  4. def __init__(self, api_client=None):
  5. self.api_client = api_client
  6. def metadata(self):
  7. number_items = {
  8. 'type': 'string',
  9. 'minLength': 1,
  10. 'maxLength': 100,
  11. }
  12. return {
  13. 'name': self.name,
  14. 'description': (
  15. '本工具只提交异步导出任务,不会在本次调用中等待文件生成。'
  16. '成功后返回任务引用(task_ref)和'
  17. '建议等待时间(retry_after_seconds)。稍后使用 query_export_task 单独查询'
  18. '任务状态或下载链接。'
  19. '使用场景:只有用户明确要求导出后台排舱单列表中的省外进港资料并需要'
  20. '下载文件时使用。'
  21. '只能在用户明确要求导出省外进港资料,并明确提供排舱单号、'
  22. '柜号、提单号或SO号中的一种,以及宁波、上海或美森资料类型时调用。'
  23. '号码类型不明确时必须先询问用户;用户没有明确号码类型时必须先询问:'
  24. '”请确认使用哪种单号导出:排舱单号、柜号、提单号还是SO号?”'
  25. '确认前不得调用。禁止使用:普通排舱查询、排舱详情、订单查询或普通文件'
  26. '查询不得调用本工具。不得根据号码格式猜测,不得跨字段或跨工具试查,'
  27. '也不得在查询失败后切换号码类型重试。一次只能选择一种号码类型。'
  28. '一次只能选择一种资料类型;用户要求多个类型时应分别调用。'
  29. '参数名仅用于工具调用;向用户回答时只能使用中文业务名称,'
  30. '不得展示内部参数名。'
  31. ),
  32. 'input_schema': {
  33. 'type': 'object',
  34. 'properties': {
  35. 'outbound_numbers': {
  36. 'type': 'array',
  37. 'items': dict(number_items),
  38. 'minItems': 1,
  39. 'maxItems': 100,
  40. 'uniqueItems': True,
  41. 'description': (
  42. '要导出的排舱单号数组。排舱单号是后台排舱单列表中的'
  43. '业务单号,不是系统订单号、客户参考号、快递单号、柜号、'
  44. 'SO号或Shipment ID。只能使用用户明确提供并确认类型为'
  45. '排舱单号的值,不得根据号码格式猜测。'
  46. ),
  47. 'examples': [[
  48. 'PC202607140001',
  49. 'PC202607140002',
  50. ]],
  51. },
  52. 'container_codes': {
  53. 'type': 'array',
  54. 'items': dict(number_items),
  55. 'minItems': 1,
  56. 'maxItems': 100,
  57. 'uniqueItems': True,
  58. 'description': (
  59. '要导出的柜号或集装箱号数组。仅当用户明确说明号码类型'
  60. '为“柜号”或“集装箱号”时使用,不得放入排舱单号或提单号。'
  61. ),
  62. 'examples': [[
  63. 'MSCU1234567',
  64. 'TGHU7654321',
  65. ]],
  66. },
  67. 'bl_numbers': {
  68. 'type': 'array',
  69. 'items': dict(number_items),
  70. 'minItems': 1,
  71. 'maxItems': 100,
  72. 'uniqueItems': True,
  73. 'description': (
  74. '要导出的提单号数组,对应后台排舱单列表中的“提单号”。'
  75. '不是系统提单号;仅当用户明确说明号码类型为“提单号”时使用。'
  76. ),
  77. 'examples': [[
  78. 'BL202607150001',
  79. 'BL202607150002',
  80. ]],
  81. },
  82. 'so_numbers': {
  83. 'type': 'array',
  84. 'items': dict(number_items),
  85. 'minItems': 1,
  86. 'maxItems': 100,
  87. 'uniqueItems': True,
  88. 'description': (
  89. '要导出的SO号数组,对应后台排舱单列表中的“SO号”,'
  90. '数据字段为fms_booking_detail.so_number。仅当用户'
  91. '明确说明号码类型为“SO号”时使用。'
  92. ),
  93. 'examples': [[
  94. 'SO202607160001',
  95. 'SO202607160002',
  96. ]],
  97. },
  98. 'file_type': {
  99. 'type': 'string',
  100. 'enum': ['NB', 'SH', 'MS'],
  101. 'description': (
  102. '进港资料类型:NB=宁波进港资料,SH=上海进港资料,'
  103. 'MS=美森进港资料。必须根据用户明确选择传值;'
  104. '用户未说明时先询问,不得默认选择。'
  105. ),
  106. 'examples': ['NB'],
  107. },
  108. },
  109. 'required': ['file_type'],
  110. 'oneOf': [
  111. {'required': ['outbound_numbers']},
  112. {'required': ['container_codes']},
  113. {'required': ['bl_numbers']},
  114. {'required': ['so_numbers']},
  115. ],
  116. 'additionalProperties': False,
  117. },
  118. }
  119. def call(
  120. self,
  121. outbound_numbers=None,
  122. file_type=None,
  123. request_id='rq_export_out_of_province_port_data',
  124. container_codes=None,
  125. bl_numbers=None,
  126. so_numbers=None,
  127. ):
  128. if self.api_client is None:
  129. raise RuntimeError(
  130. 'api client is required for export_out_of_province_port_data'
  131. )
  132. number_fields = {
  133. 'outbound_numbers': outbound_numbers,
  134. 'container_codes': container_codes,
  135. 'bl_numbers': bl_numbers,
  136. 'so_numbers': so_numbers,
  137. }
  138. provided = [
  139. field for field, values in number_fields.items()
  140. if values is not None
  141. ]
  142. if len(provided) != 1:
  143. raise ValueError('provide exactly one number type')
  144. number_field = provided[0]
  145. numbers = self._normalize_numbers(number_fields[number_field], number_field)
  146. normalized_type = str(file_type or '').strip().upper()
  147. if normalized_type not in ('NB', 'SH', 'MS'):
  148. raise ValueError('file_type must be NB, SH, or MS')
  149. return self.api_client.call_tool(
  150. self.name,
  151. self.route_path,
  152. {
  153. number_field: numbers,
  154. 'file_type': normalized_type,
  155. },
  156. request_id,
  157. )
  158. @staticmethod
  159. def _normalize_numbers(values, field):
  160. if not isinstance(values, list):
  161. raise ValueError('{0} must be an array'.format(field))
  162. if len(values) > 100:
  163. raise ValueError('{0} must contain 1 to 100 values'.format(field))
  164. numbers = []
  165. for value in values:
  166. if not isinstance(value, str):
  167. raise ValueError('{0} must contain strings'.format(field))
  168. value = value.strip()
  169. if not value:
  170. continue
  171. if len(value) > 100:
  172. raise ValueError('{0} contains a number that is too long'.format(field))
  173. if value not in numbers:
  174. numbers.append(value)
  175. if not numbers or len(numbers) > 100:
  176. raise ValueError('{0} must contain 1 to 100 values'.format(field))
  177. return numbers