datasets_segments.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. # -*- coding:utf-8 -*-
  2. import uuid
  3. from datetime import datetime
  4. from flask import request
  5. from flask_login import current_user
  6. from flask_restful import Resource, reqparse, marshal
  7. from werkzeug.exceptions import NotFound, Forbidden
  8. import services
  9. from controllers.console import api
  10. from controllers.console.app.error import ProviderNotInitializeError
  11. from controllers.console.datasets.error import InvalidActionError, NoFileUploadedError, TooManyFilesError
  12. from controllers.console.setup import setup_required
  13. from controllers.console.wraps import account_initialization_required
  14. from core.model_providers.error import LLMBadRequestError, ProviderTokenNotInitError
  15. from core.model_providers.model_factory import ModelFactory
  16. from core.login.login import login_required
  17. from extensions.ext_database import db
  18. from extensions.ext_redis import redis_client
  19. from fields.segment_fields import segment_fields
  20. from models.dataset import DocumentSegment
  21. from libs.helper import TimestampField
  22. from services.dataset_service import DatasetService, DocumentService, SegmentService
  23. from tasks.enable_segment_to_index_task import enable_segment_to_index_task
  24. from tasks.disable_segment_from_index_task import disable_segment_from_index_task
  25. from tasks.batch_create_segment_to_index_task import batch_create_segment_to_index_task
  26. import pandas as pd
  27. class DatasetDocumentSegmentListApi(Resource):
  28. @setup_required
  29. @login_required
  30. @account_initialization_required
  31. def get(self, dataset_id, document_id):
  32. dataset_id = str(dataset_id)
  33. document_id = str(document_id)
  34. dataset = DatasetService.get_dataset(dataset_id)
  35. if not dataset:
  36. raise NotFound('Dataset not found.')
  37. try:
  38. DatasetService.check_dataset_permission(dataset, current_user)
  39. except services.errors.account.NoPermissionError as e:
  40. raise Forbidden(str(e))
  41. document = DocumentService.get_document(dataset_id, document_id)
  42. if not document:
  43. raise NotFound('Document not found.')
  44. parser = reqparse.RequestParser()
  45. parser.add_argument('last_id', type=str, default=None, location='args')
  46. parser.add_argument('limit', type=int, default=20, location='args')
  47. parser.add_argument('status', type=str,
  48. action='append', default=[], location='args')
  49. parser.add_argument('hit_count_gte', type=int,
  50. default=None, location='args')
  51. parser.add_argument('enabled', type=str, default='all', location='args')
  52. parser.add_argument('keyword', type=str, default=None, location='args')
  53. args = parser.parse_args()
  54. last_id = args['last_id']
  55. limit = min(args['limit'], 100)
  56. status_list = args['status']
  57. hit_count_gte = args['hit_count_gte']
  58. keyword = args['keyword']
  59. query = DocumentSegment.query.filter(
  60. DocumentSegment.document_id == str(document_id),
  61. DocumentSegment.tenant_id == current_user.current_tenant_id
  62. )
  63. if last_id is not None:
  64. last_segment = DocumentSegment.query.get(str(last_id))
  65. if last_segment:
  66. query = query.filter(
  67. DocumentSegment.position > last_segment.position)
  68. else:
  69. return {'data': [], 'has_more': False, 'limit': limit}, 200
  70. if status_list:
  71. query = query.filter(DocumentSegment.status.in_(status_list))
  72. if hit_count_gte is not None:
  73. query = query.filter(DocumentSegment.hit_count >= hit_count_gte)
  74. if keyword:
  75. query = query.where(DocumentSegment.content.ilike(f'%{keyword}%'))
  76. if args['enabled'].lower() != 'all':
  77. if args['enabled'].lower() == 'true':
  78. query = query.filter(DocumentSegment.enabled == True)
  79. elif args['enabled'].lower() == 'false':
  80. query = query.filter(DocumentSegment.enabled == False)
  81. total = query.count()
  82. segments = query.order_by(DocumentSegment.position).limit(limit + 1).all()
  83. has_more = False
  84. if len(segments) > limit:
  85. has_more = True
  86. segments = segments[:-1]
  87. return {
  88. 'data': marshal(segments, segment_fields),
  89. 'doc_form': document.doc_form,
  90. 'has_more': has_more,
  91. 'limit': limit,
  92. 'total': total
  93. }, 200
  94. class DatasetDocumentSegmentApi(Resource):
  95. @setup_required
  96. @login_required
  97. @account_initialization_required
  98. def patch(self, dataset_id, segment_id, action):
  99. dataset_id = str(dataset_id)
  100. dataset = DatasetService.get_dataset(dataset_id)
  101. if not dataset:
  102. raise NotFound('Dataset not found.')
  103. # check user's model setting
  104. DatasetService.check_dataset_model_setting(dataset)
  105. # The role of the current user in the ta table must be admin or owner
  106. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  107. raise Forbidden()
  108. try:
  109. DatasetService.check_dataset_permission(dataset, current_user)
  110. except services.errors.account.NoPermissionError as e:
  111. raise Forbidden(str(e))
  112. if dataset.indexing_technique == 'high_quality':
  113. # check embedding model setting
  114. try:
  115. ModelFactory.get_embedding_model(
  116. tenant_id=current_user.current_tenant_id,
  117. model_provider_name=dataset.embedding_model_provider,
  118. model_name=dataset.embedding_model
  119. )
  120. except LLMBadRequestError:
  121. raise ProviderNotInitializeError(
  122. f"No Embedding Model available. Please configure a valid provider "
  123. f"in the Settings -> Model Provider.")
  124. except ProviderTokenNotInitError as ex:
  125. raise ProviderNotInitializeError(ex.description)
  126. segment = DocumentSegment.query.filter(
  127. DocumentSegment.id == str(segment_id),
  128. DocumentSegment.tenant_id == current_user.current_tenant_id
  129. ).first()
  130. if not segment:
  131. raise NotFound('Segment not found.')
  132. document_indexing_cache_key = 'document_{}_indexing'.format(segment.document_id)
  133. cache_result = redis_client.get(document_indexing_cache_key)
  134. if cache_result is not None:
  135. raise InvalidActionError("Document is being indexed, please try again later")
  136. indexing_cache_key = 'segment_{}_indexing'.format(segment.id)
  137. cache_result = redis_client.get(indexing_cache_key)
  138. if cache_result is not None:
  139. raise InvalidActionError("Segment is being indexed, please try again later")
  140. if action == "enable":
  141. if segment.enabled:
  142. raise InvalidActionError("Segment is already enabled.")
  143. segment.enabled = True
  144. segment.disabled_at = None
  145. segment.disabled_by = None
  146. db.session.commit()
  147. # Set cache to prevent indexing the same segment multiple times
  148. redis_client.setex(indexing_cache_key, 600, 1)
  149. enable_segment_to_index_task.delay(segment.id)
  150. return {'result': 'success'}, 200
  151. elif action == "disable":
  152. if not segment.enabled:
  153. raise InvalidActionError("Segment is already disabled.")
  154. segment.enabled = False
  155. segment.disabled_at = datetime.utcnow()
  156. segment.disabled_by = current_user.id
  157. db.session.commit()
  158. # Set cache to prevent indexing the same segment multiple times
  159. redis_client.setex(indexing_cache_key, 600, 1)
  160. disable_segment_from_index_task.delay(segment.id)
  161. return {'result': 'success'}, 200
  162. else:
  163. raise InvalidActionError()
  164. class DatasetDocumentSegmentAddApi(Resource):
  165. @setup_required
  166. @login_required
  167. @account_initialization_required
  168. def post(self, dataset_id, document_id):
  169. # check dataset
  170. dataset_id = str(dataset_id)
  171. dataset = DatasetService.get_dataset(dataset_id)
  172. if not dataset:
  173. raise NotFound('Dataset not found.')
  174. # check document
  175. document_id = str(document_id)
  176. document = DocumentService.get_document(dataset_id, document_id)
  177. if not document:
  178. raise NotFound('Document not found.')
  179. # The role of the current user in the ta table must be admin or owner
  180. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  181. raise Forbidden()
  182. # check embedding model setting
  183. if dataset.indexing_technique == 'high_quality':
  184. try:
  185. ModelFactory.get_embedding_model(
  186. tenant_id=current_user.current_tenant_id,
  187. model_provider_name=dataset.embedding_model_provider,
  188. model_name=dataset.embedding_model
  189. )
  190. except LLMBadRequestError:
  191. raise ProviderNotInitializeError(
  192. f"No Embedding Model available. Please configure a valid provider "
  193. f"in the Settings -> Model Provider.")
  194. except ProviderTokenNotInitError as ex:
  195. raise ProviderNotInitializeError(ex.description)
  196. try:
  197. DatasetService.check_dataset_permission(dataset, current_user)
  198. except services.errors.account.NoPermissionError as e:
  199. raise Forbidden(str(e))
  200. # validate args
  201. parser = reqparse.RequestParser()
  202. parser.add_argument('content', type=str, required=True, nullable=False, location='json')
  203. parser.add_argument('answer', type=str, required=False, nullable=True, location='json')
  204. parser.add_argument('keywords', type=list, required=False, nullable=True, location='json')
  205. args = parser.parse_args()
  206. SegmentService.segment_create_args_validate(args, document)
  207. segment = SegmentService.create_segment(args, document, dataset)
  208. return {
  209. 'data': marshal(segment, segment_fields),
  210. 'doc_form': document.doc_form
  211. }, 200
  212. class DatasetDocumentSegmentUpdateApi(Resource):
  213. @setup_required
  214. @login_required
  215. @account_initialization_required
  216. def patch(self, dataset_id, document_id, segment_id):
  217. # check dataset
  218. dataset_id = str(dataset_id)
  219. dataset = DatasetService.get_dataset(dataset_id)
  220. if not dataset:
  221. raise NotFound('Dataset not found.')
  222. # check user's model setting
  223. DatasetService.check_dataset_model_setting(dataset)
  224. # check document
  225. document_id = str(document_id)
  226. document = DocumentService.get_document(dataset_id, document_id)
  227. if not document:
  228. raise NotFound('Document not found.')
  229. if dataset.indexing_technique == 'high_quality':
  230. # check embedding model setting
  231. try:
  232. ModelFactory.get_embedding_model(
  233. tenant_id=current_user.current_tenant_id,
  234. model_provider_name=dataset.embedding_model_provider,
  235. model_name=dataset.embedding_model
  236. )
  237. except LLMBadRequestError:
  238. raise ProviderNotInitializeError(
  239. f"No Embedding Model available. Please configure a valid provider "
  240. f"in the Settings -> Model Provider.")
  241. except ProviderTokenNotInitError as ex:
  242. raise ProviderNotInitializeError(ex.description)
  243. # check segment
  244. segment_id = str(segment_id)
  245. segment = DocumentSegment.query.filter(
  246. DocumentSegment.id == str(segment_id),
  247. DocumentSegment.tenant_id == current_user.current_tenant_id
  248. ).first()
  249. if not segment:
  250. raise NotFound('Segment not found.')
  251. # The role of the current user in the ta table must be admin or owner
  252. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  253. raise Forbidden()
  254. try:
  255. DatasetService.check_dataset_permission(dataset, current_user)
  256. except services.errors.account.NoPermissionError as e:
  257. raise Forbidden(str(e))
  258. # validate args
  259. parser = reqparse.RequestParser()
  260. parser.add_argument('content', type=str, required=True, nullable=False, location='json')
  261. parser.add_argument('answer', type=str, required=False, nullable=True, location='json')
  262. parser.add_argument('keywords', type=list, required=False, nullable=True, location='json')
  263. args = parser.parse_args()
  264. SegmentService.segment_create_args_validate(args, document)
  265. segment = SegmentService.update_segment(args, segment, document, dataset)
  266. return {
  267. 'data': marshal(segment, segment_fields),
  268. 'doc_form': document.doc_form
  269. }, 200
  270. @setup_required
  271. @login_required
  272. @account_initialization_required
  273. def delete(self, dataset_id, document_id, segment_id):
  274. # check dataset
  275. dataset_id = str(dataset_id)
  276. dataset = DatasetService.get_dataset(dataset_id)
  277. if not dataset:
  278. raise NotFound('Dataset not found.')
  279. # check user's model setting
  280. DatasetService.check_dataset_model_setting(dataset)
  281. # check document
  282. document_id = str(document_id)
  283. document = DocumentService.get_document(dataset_id, document_id)
  284. if not document:
  285. raise NotFound('Document not found.')
  286. # check segment
  287. segment_id = str(segment_id)
  288. segment = DocumentSegment.query.filter(
  289. DocumentSegment.id == str(segment_id),
  290. DocumentSegment.tenant_id == current_user.current_tenant_id
  291. ).first()
  292. if not segment:
  293. raise NotFound('Segment not found.')
  294. # The role of the current user in the ta table must be admin or owner
  295. if current_user.current_tenant.current_role not in ['admin', 'owner']:
  296. raise Forbidden()
  297. try:
  298. DatasetService.check_dataset_permission(dataset, current_user)
  299. except services.errors.account.NoPermissionError as e:
  300. raise Forbidden(str(e))
  301. SegmentService.delete_segment(segment, document, dataset)
  302. return {'result': 'success'}, 200
  303. class DatasetDocumentSegmentBatchImportApi(Resource):
  304. @setup_required
  305. @login_required
  306. @account_initialization_required
  307. def post(self, dataset_id, document_id):
  308. # check dataset
  309. dataset_id = str(dataset_id)
  310. dataset = DatasetService.get_dataset(dataset_id)
  311. if not dataset:
  312. raise NotFound('Dataset not found.')
  313. # check document
  314. document_id = str(document_id)
  315. document = DocumentService.get_document(dataset_id, document_id)
  316. if not document:
  317. raise NotFound('Document not found.')
  318. # get file from request
  319. file = request.files['file']
  320. # check file
  321. if 'file' not in request.files:
  322. raise NoFileUploadedError()
  323. if len(request.files) > 1:
  324. raise TooManyFilesError()
  325. # check file type
  326. if not file.filename.endswith('.csv'):
  327. raise ValueError("Invalid file type. Only CSV files are allowed")
  328. try:
  329. # Skip the first row
  330. df = pd.read_csv(file)
  331. result = []
  332. for index, row in df.iterrows():
  333. if document.doc_form == 'qa_model':
  334. data = {'content': row[0], 'answer': row[1]}
  335. else:
  336. data = {'content': row[0]}
  337. result.append(data)
  338. if len(result) == 0:
  339. raise ValueError("The CSV file is empty.")
  340. # async job
  341. job_id = str(uuid.uuid4())
  342. indexing_cache_key = 'segment_batch_import_{}'.format(str(job_id))
  343. # send batch add segments task
  344. redis_client.setnx(indexing_cache_key, 'waiting')
  345. batch_create_segment_to_index_task.delay(str(job_id), result, dataset_id, document_id,
  346. current_user.current_tenant_id, current_user.id)
  347. except Exception as e:
  348. return {'error': str(e)}, 500
  349. return {
  350. 'job_id': job_id,
  351. 'job_status': 'waiting'
  352. }, 200
  353. @setup_required
  354. @login_required
  355. @account_initialization_required
  356. def get(self, job_id):
  357. job_id = str(job_id)
  358. indexing_cache_key = 'segment_batch_import_{}'.format(job_id)
  359. cache_result = redis_client.get(indexing_cache_key)
  360. if cache_result is None:
  361. raise ValueError("The job is not exist.")
  362. return {
  363. 'job_id': job_id,
  364. 'job_status': cache_result.decode()
  365. }, 200
  366. api.add_resource(DatasetDocumentSegmentListApi,
  367. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments')
  368. api.add_resource(DatasetDocumentSegmentApi,
  369. '/datasets/<uuid:dataset_id>/segments/<uuid:segment_id>/<string:action>')
  370. api.add_resource(DatasetDocumentSegmentAddApi,
  371. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment')
  372. api.add_resource(DatasetDocumentSegmentUpdateApi,
  373. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>')
  374. api.add_resource(DatasetDocumentSegmentBatchImportApi,
  375. '/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/batch_import',
  376. '/datasets/batch_import_status/<uuid:job_id>')