save_headhaul_document.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. class SaveHeadhaulDocumentTool:
  2. name = 'save_headhaul_document'
  3. route_path = '/mcp/tools/saveHeadhaulDocument'
  4. def __init__(self, api_client=None):
  5. self.api_client = api_client
  6. def metadata(self):
  7. return {
  8. 'name': self.name,
  9. 'description': (
  10. '使用场景:上传成功返回ticket_ref之后,或用户只要外链时,'
  11. '用本工具保存一份新的头程文档。必须带添加文档类型和至少一条已确认种类的录入单号。'
  12. '添加文档类型的显示名是「文档类型名称 - 文件维度」。'
  13. '用户只说了类型名称且不止一条对得上时,先列出完整显示名请用户选择,选定前不要保存。'
  14. '只有一条对得上时使用该条 value。'
  15. 'ticket_ref和外链不能同时提交。没有行内编辑;要改只能删除后重加。'
  16. '禁止使用:手填OSS路径、混放号码种类、把文件放进工具参数。'
  17. '参数只用于工具内部调用;最终回答只能展示中文业务名称。'
  18. ),
  19. 'input_schema': {
  20. 'type': 'object',
  21. 'properties': {
  22. 'document_type_id': {
  23. 'type': 'integer',
  24. 'minimum': 1,
  25. 'description': (
  26. '要添加的文档类型。必须先调用'
  27. 'list_headhaul_document_filter_options并选择“添加文档类型”。'
  28. '显示名含文件维度。不止一条对得上类型名称时,先请用户按完整显示名选择。'
  29. ),
  30. },
  31. 'ticket_ref': {
  32. 'type': 'string',
  33. 'minLength': 1,
  34. 'maxLength': 500,
  35. 'description': '上传成功后的一次性凭证,不能手填。',
  36. },
  37. 'link_url': {
  38. 'type': 'string',
  39. 'minLength': 8,
  40. 'maxLength': 1000,
  41. 'description': 'http或https外链;与ticket_ref二选一。',
  42. },
  43. 'entries': {
  44. 'type': 'array',
  45. 'minItems': 1,
  46. 'items': {
  47. 'type': 'object',
  48. 'properties': {
  49. 'entry_no_type': {
  50. 'type': 'integer',
  51. 'minimum': 1,
  52. 'maximum': 24,
  53. },
  54. 'numbers': {
  55. 'type': 'array',
  56. 'minItems': 1,
  57. 'maxItems': 50,
  58. 'items': {
  59. 'type': 'string',
  60. 'minLength': 1,
  61. 'maxLength': 100,
  62. },
  63. },
  64. },
  65. 'required': ['entry_no_type', 'numbers'],
  66. 'additionalProperties': False,
  67. },
  68. 'description': '录入单号。entry_no_type来自录入单号种类筛选项。',
  69. },
  70. },
  71. 'required': ['document_type_id', 'entries'],
  72. 'additionalProperties': False,
  73. },
  74. }
  75. def call(
  76. self,
  77. document_type_id,
  78. entries,
  79. ticket_ref=None,
  80. link_url=None,
  81. request_id='rq_save_headhaul_document',
  82. ):
  83. if self.api_client is None:
  84. raise RuntimeError(
  85. 'api client is required for save_headhaul_document'
  86. )
  87. if (
  88. isinstance(document_type_id, bool)
  89. or not isinstance(document_type_id, int)
  90. or document_type_id < 1
  91. ):
  92. raise ValueError('document_type_id is invalid')
  93. ticket = '' if ticket_ref is None else str(ticket_ref).strip()
  94. link = '' if link_url is None else str(link_url).strip()
  95. if (ticket != '' and link != '') or (ticket == '' and link == ''):
  96. raise ValueError('ticket_ref and link_url are exclusive')
  97. if link and not (
  98. link.lower().startswith('http://')
  99. or link.lower().startswith('https://')
  100. ):
  101. raise ValueError('link_url is invalid')
  102. payload = {
  103. 'document_type_id': document_type_id,
  104. 'entries': self._entries(entries),
  105. }
  106. if ticket:
  107. payload['ticket_ref'] = ticket
  108. if link:
  109. payload['link_url'] = link
  110. return self.api_client.call_tool(
  111. self.name, self.route_path, payload, request_id,
  112. )
  113. @staticmethod
  114. def _entries(values):
  115. if not isinstance(values, list) or not values:
  116. raise ValueError('entries is invalid')
  117. result = []
  118. for item in values:
  119. if not isinstance(item, dict):
  120. raise ValueError('entries is invalid')
  121. type_id = item.get('entry_no_type')
  122. numbers = item.get('numbers')
  123. if (
  124. isinstance(type_id, bool)
  125. or not isinstance(type_id, int)
  126. or type_id < 1
  127. or type_id > 24
  128. ):
  129. raise ValueError('entry_no_type is invalid')
  130. if not isinstance(numbers, list) or not numbers or len(numbers) > 50:
  131. raise ValueError('numbers is invalid')
  132. clean = []
  133. for number in numbers:
  134. if not isinstance(number, str):
  135. raise ValueError('numbers is invalid')
  136. number = number.strip()
  137. if not number or len(number) > 100:
  138. raise ValueError('numbers is invalid')
  139. if number not in clean:
  140. clean.append(number)
  141. result.append({'entry_no_type': type_id, 'numbers': clean})
  142. return result