datasets_segments.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # -*- coding:utf-8 -*-
  2. from datetime import datetime
  3. from flask_login import login_required, current_user
  4. from flask_restful import Resource, reqparse, fields, marshal
  5. from werkzeug.exceptions import NotFound, Forbidden
  6. import services
  7. from controllers.console import api
  8. from controllers.console.datasets.error import InvalidActionError
  9. from controllers.console.setup import setup_required
  10. from controllers.console.wraps import account_initialization_required
  11. from extensions.ext_database import db
  12. from extensions.ext_redis import redis_client
  13. from models.dataset import DocumentSegment
  14. from libs.helper import TimestampField
  15. from services.dataset_service import DatasetService, DocumentService, SegmentService
  16. from tasks.enable_segment_to_index_task import enable_segment_to_index_task
  17. from tasks.remove_segment_from_index_task import remove_segment_from_index_task
  18. segment_fields = {
  19. 'id': fields.String,
  20. 'position': fields.Integer,
  21. 'document_id': fields.String,
  22. 'content': fields.String,
  23. 'answer': fields.String,
  24. 'word_count': fields.Integer,
  25. 'tokens': fields.Integer,
  26. 'keywords': fields.List(fields.String),
  27. 'index_node_id': fields.String,
  28. 'index_node_hash': fields.String,
  29. 'hit_count': fields.Integer,
  30. 'enabled': fields.Boolean,
  31. 'disabled_at': TimestampField,
  32. 'disabled_by': fields.String,
  33. 'status': fields.String,
  34. 'created_by': fields.String,
  35. 'created_at': TimestampField,
  36. 'indexing_at': TimestampField,
  37. 'completed_at': TimestampField,
  38. 'error': fields.String,
  39. 'stopped_at': TimestampField
  40. }
  41. segment_list_response = {
  42. 'data': fields.List(fields.Nested(segment_fields)),
  43. 'has_more': fields.Boolean,
  44. 'limit': fields.Integer
  45. }
  46. class DatasetDocumentSegmentListApi(Resource):
  47. @setup_required
  48. @login_required
  49. @account_initialization_required
  50. def get(self, dataset_id, document_id):
  51. dataset_id = str(dataset_id)
  52. document_id = str(document_id)
  53. dataset = DatasetService.get_dataset(dataset_id)
  54. if not dataset:
  55. raise NotFound('Dataset not found.')
  56. try:
  57. DatasetService.check_dataset_permission(dataset, current_user)
  58. except services.errors.account.NoPermissionError as e:
  59. raise Forbidden(str(e))
  60. document = DocumentService.get_document(dataset_id, document_id)
  61. if not document:
  62. raise NotFound('Document not found.')
  63. parser = reqparse.RequestParser()
  64. parser.add_argument('last_id', type=str, default=None, location='args')
  65. parser.add_argument('limit', type=int, default=20, location='args')
  66. parser.add_argument('status', type=str,
  67. action='append', default=[], location='args')
  68. parser.add_argument('hit_count_gte', type=int,
  69. default=None, location='args')
  70. parser.add_argument('enabled', type=str, default='all', location='args')
  71. parser.add_argument('keyword', type=str, default=None, location='args')
  72. args = parser.parse_args()
  73. last_id = args['last_id']
  74. limit = min(args['limit'], 100)
  75. status_list = args['status']
  76. hit_count_gte = args['hit_count_gte']
  77. keyword = args['keyword']
  78. query = DocumentSegment.query.filter(
  79. DocumentSegment.document_id == str(document_id),
  80. DocumentSegment.tenant_id == current_user.current_tenant_id
  81. )
  82. if last_id is not None:
  83. last_segment = DocumentSegment.query.get(str(last_id))
  84. if last_segment:
  85. query = query.filter(
  86. DocumentSegment.position > last_segment.position)
  87. else:
  88. return {'data': [], 'has_more': False, 'limit': limit}, 200
  89. if status_list:
  90. query = query.filter(DocumentSegment.status.in_(status_list))
  91. if hit_count_gte is not None:
  92. query = query.filter(DocumentSegment.hit_count >= hit_count_gte)
  93. if keyword:
  94. query = query.where(DocumentSegment.content.ilike(f'%{keyword}%'))
  95. if args['enabled'].lower() != 'all':
  96. if args['enabled'].lower() == 'true':
  97. query = query.filter(DocumentSegment.enabled == True)
  98. elif args['enabled'].lower() == 'false':
  99. query = query.filter(DocumentSegment.enabled == False)
  100. total = query.count()
  101. segments = query.order_by(DocumentSegment.position).limit(limit + 1).all()
  102. has_more = False
  103. if len(segments) > limit:
  104. has_more = True
  105. segments = segments[:-1]
  106. return {
  107. 'data': marshal(segments, segment_fields),
  108. 'doc_form': document.doc_form,
  109. 'has_more': has_more,
  110. 'limit': limit,
  111. 'total': total
  112. }, 200
  113. class DatasetDocumentSegmentApi(Resource):
  114. @setup_required
  115. @login_required
  116. @account_initialization_required
  117. def patch(self, dataset_id, segment_id, action):
  118. dataset_id = str(dataset_id)
  119. dataset = DatasetService.get_dataset(dataset_id)
  120. if not dataset:
  121. raise NotFound('Dataset not found.')
  122. # The role of the current user in the ta table must be admin or owner
  123. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  124. raise Forbidden()
  125. try:
  126. DatasetService.check_dataset_permission(dataset, current_user)
  127. except services.errors.account.NoPermissionError as e:
  128. raise Forbidden(str(e))
  129. segment = DocumentSegment.query.filter(
  130. DocumentSegment.id == str(segment_id),
  131. DocumentSegment.tenant_id == current_user.current_tenant_id
  132. ).first()
  133. if not segment:
  134. raise NotFound('Segment not found.')
  135. document_indexing_cache_key = 'document_{}_indexing'.format(segment.document_id)
  136. cache_result = redis_client.get(document_indexing_cache_key)
  137. if cache_result is not None:
  138. raise InvalidActionError("Document is being indexed, please try again later")
  139. indexing_cache_key = 'segment_{}_indexing'.format(segment.id)
  140. cache_result = redis_client.get(indexing_cache_key)
  141. if cache_result is not None:
  142. raise InvalidActionError("Segment is being indexed, please try again later")
  143. if action == "enable":
  144. if segment.enabled:
  145. raise InvalidActionError("Segment is already enabled.")
  146. segment.enabled = True
  147. segment.disabled_at = None
  148. segment.disabled_by = None
  149. db.session.commit()
  150. # Set cache to prevent indexing the same segment multiple times
  151. redis_client.setex(indexing_cache_key, 600, 1)
  152. enable_segment_to_index_task.delay(segment.id)
  153. return {'result': 'success'}, 200
  154. elif action == "disable":
  155. if not segment.enabled:
  156. raise InvalidActionError("Segment is already disabled.")
  157. segment.enabled = False
  158. segment.disabled_at = datetime.utcnow()
  159. segment.disabled_by = current_user.id
  160. db.session.commit()
  161. # Set cache to prevent indexing the same segment multiple times
  162. redis_client.setex(indexing_cache_key, 600, 1)
  163. remove_segment_from_index_task.delay(segment.id)
  164. return {'result': 'success'}, 200
  165. else:
  166. raise InvalidActionError()
  167. class DatasetDocumentSegmentAddApi(Resource):
  168. @setup_required
  169. @login_required
  170. @account_initialization_required
  171. def post(self, dataset_id, document_id):
  172. # check dataset
  173. dataset_id = str(dataset_id)
  174. dataset = DatasetService.get_dataset(dataset_id)
  175. if not dataset:
  176. raise NotFound('Dataset not found.')
  177. # check document
  178. document_id = str(document_id)
  179. document = DocumentService.get_document(dataset_id, document_id)
  180. if not document:
  181. raise NotFound('Document not found.')
  182. # The role of the current user in the ta table must be admin or owner
  183. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  184. raise Forbidden()
  185. try:
  186. DatasetService.check_dataset_permission(dataset, current_user)
  187. except services.errors.account.NoPermissionError as e:
  188. raise Forbidden(str(e))
  189. # validate args
  190. parser = reqparse.RequestParser()
  191. parser.add_argument('content', type=str, required=True, nullable=False, location='json')
  192. parser.add_argument('answer', type=str, required=False, nullable=True, location='json')
  193. parser.add_argument('keywords', type=list, required=False, nullable=True, location='json')
  194. args = parser.parse_args()
  195. SegmentService.segment_create_args_validate(args, document)
  196. segment = SegmentService.create_segment(args, document)
  197. return {
  198. 'data': marshal(segment, segment_fields),
  199. 'doc_form': document.doc_form
  200. }, 200
  201. class DatasetDocumentSegmentUpdateApi(Resource):
  202. @setup_required
  203. @login_required
  204. @account_initialization_required
  205. def patch(self, dataset_id, document_id, segment_id):
  206. # check dataset
  207. dataset_id = str(dataset_id)
  208. dataset = DatasetService.get_dataset(dataset_id)
  209. if not dataset:
  210. raise NotFound('Dataset not found.')
  211. # check document
  212. document_id = str(document_id)
  213. document = DocumentService.get_document(dataset_id, document_id)
  214. if not document:
  215. raise NotFound('Document not found.')
  216. # check segment
  217. segment_id = str(segment_id)
  218. segment = DocumentSegment.query.filter(
  219. DocumentSegment.id == str(segment_id),
  220. DocumentSegment.tenant_id == current_user.current_tenant_id
  221. ).first()
  222. if not segment:
  223. raise NotFound('Segment not found.')
  224. # The role of the current user in the ta table must be admin or owner
  225. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  226. raise Forbidden()
  227. try:
  228. DatasetService.check_dataset_permission(dataset, current_user)
  229. except services.errors.account.NoPermissionError as e:
  230. raise Forbidden(str(e))
  231. # validate args
  232. parser = reqparse.RequestParser()
  233. parser.add_argument('content', type=str, required=True, nullable=False, location='json')
  234. parser.add_argument('answer', type=str, required=False, nullable=True, location='json')
  235. parser.add_argument('keywords', type=list, required=False, nullable=True, location='json')
  236. args = parser.parse_args()
  237. SegmentService.segment_create_args_validate(args, document)
  238. segment = SegmentService.update_segment(args, segment, document)
  239. return {
  240. 'data': marshal(segment, segment_fields),
  241. 'doc_form': document.doc_form
  242. }, 200
  243. api.add_resource(DatasetDocumentSegmentListApi,
  244. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments')
  245. api.add_resource(DatasetDocumentSegmentApi,
  246. '/datasets/<uuid:dataset_id>/segments/<uuid:segment_id>/<string:action>')
  247. api.add_resource(DatasetDocumentSegmentAddApi,
  248. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment')
  249. api.add_resource(DatasetDocumentSegmentUpdateApi,
  250. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>')