trial_goods_tax.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import math
  2. class TrialGoodsTaxTool:
  3. name = 'trial_goods_tax'
  4. route_path = '/mcp/tools/trialGoodsTax'
  5. def __init__(self, api_client=None):
  6. self.api_client = api_client
  7. def metadata(self):
  8. number = {'type': 'number', 'exclusiveMinimum': 0, 'maximum': 1000000000}
  9. country = {
  10. 'type': 'string',
  11. 'minLength': 2,
  12. 'maxLength': 3,
  13. 'pattern': '^[A-Za-z]{2,3}$',
  14. 'description': '国家代码,例如 CN、US。不得传中文国名或“国家名-代码”。',
  15. }
  16. return {
  17. 'name': self.name,
  18. 'description': (
  19. '使用场景:用户明确要对某一条已审核商品做关税试算时使用。'
  20. '必须同时提供 SKU、起运国国家代码、目的国国家代码、采购单价、SKU数量、总毛重和箱数。'
  21. '起运国和目的国只能填国家代码,例如 CN、US。'
  22. '同一 SKU 有多条时,号码类型不明确时必须先询问用户,国家不明确也必须先询问,确认前不得调用。'
  23. '不得根据号码格式猜测,不得跨字段或跨工具试查。'
  24. '返回预计关税金额,不保存试算结果。'
  25. '禁止使用:不查询商品列表,不修改商品或清关资料,不创建订单,不导出文件。'
  26. '查商品资料用 query_goods_info。'
  27. ),
  28. 'input_schema': {
  29. 'type': 'object',
  30. 'properties': {
  31. 'sku': {
  32. 'type': 'string',
  33. 'minLength': 1,
  34. 'maxLength': 100,
  35. 'description': 'SKU。仅当用户明确说明是 SKU 时使用。',
  36. },
  37. 'departure_country': dict(country, description='起运国。'),
  38. 'destination_country': dict(country, description='目的国。'),
  39. 'price': dict(number, description='采购单价,必须大于 0。'),
  40. 'qty': dict(number, description='SKU数量,必须大于 0。'),
  41. 'gross_weight': dict(number, description='总毛重,必须大于 0。'),
  42. 'box': dict(number, description='箱数,必须大于 0。'),
  43. },
  44. 'required': [
  45. 'sku', 'departure_country', 'destination_country',
  46. 'price', 'qty', 'gross_weight', 'box',
  47. ],
  48. 'additionalProperties': False,
  49. },
  50. }
  51. def call(
  52. self,
  53. sku='',
  54. departure_country='',
  55. destination_country='',
  56. price=None,
  57. qty=None,
  58. gross_weight=None,
  59. box=None,
  60. request_id='rq_trial_goods_tax',
  61. **_ignored,
  62. ):
  63. if self.api_client is None:
  64. raise RuntimeError('api client is required for trial_goods_tax')
  65. payload = {
  66. 'sku': self._text(sku, 'sku', 100),
  67. 'departure_country': self._country(departure_country, 'departure_country'),
  68. 'destination_country': self._country(destination_country, 'destination_country'),
  69. 'price': self._positive(price, 'price'),
  70. 'qty': self._positive(qty, 'qty'),
  71. 'gross_weight': self._positive(gross_weight, 'gross_weight'),
  72. 'box': self._positive(box, 'box'),
  73. }
  74. if not payload['sku'] or not payload['departure_country'] or not payload['destination_country']:
  75. raise ValueError('sku, departure_country and destination_country are required')
  76. return self.api_client.call_tool(
  77. self.name, self.route_path, payload, request_id
  78. )
  79. @staticmethod
  80. def _country(value, field):
  81. if not isinstance(value, str):
  82. raise ValueError(field + ' must be a country code')
  83. value = value.strip().upper()
  84. if (
  85. len(value) < 2 or len(value) > 3
  86. or not value.isascii() or not value.isalpha()
  87. ):
  88. raise ValueError(field + ' must be a country code')
  89. return value
  90. @staticmethod
  91. def _text(value, field, max_length):
  92. if not isinstance(value, str):
  93. raise ValueError(field + ' must be a string')
  94. value = value.strip()
  95. if not value or len(value) > max_length:
  96. raise ValueError(field + ' is invalid')
  97. return value
  98. @staticmethod
  99. def _positive(value, field):
  100. if isinstance(value, bool) or not isinstance(value, (int, float)):
  101. raise ValueError(field + ' is invalid')
  102. if not math.isfinite(value) or value <= 0 or value > 1000000000:
  103. raise ValueError(field + ' is invalid')
  104. return value