datasets_segments.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. import uuid
  2. from flask import request
  3. from flask_login import current_user
  4. from flask_restx import Resource, marshal, reqparse
  5. from sqlalchemy import select
  6. from werkzeug.exceptions import Forbidden, NotFound
  7. import services
  8. from controllers.console import api
  9. from controllers.console.app.error import ProviderNotInitializeError
  10. from controllers.console.datasets.error import (
  11. ChildChunkDeleteIndexError,
  12. ChildChunkIndexingError,
  13. InvalidActionError,
  14. )
  15. from controllers.console.wraps import (
  16. account_initialization_required,
  17. cloud_edition_billing_knowledge_limit_check,
  18. cloud_edition_billing_rate_limit_check,
  19. cloud_edition_billing_resource_check,
  20. setup_required,
  21. )
  22. from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError
  23. from core.model_manager import ModelManager
  24. from core.model_runtime.entities.model_entities import ModelType
  25. from extensions.ext_database import db
  26. from extensions.ext_redis import redis_client
  27. from fields.segment_fields import child_chunk_fields, segment_fields
  28. from libs.login import login_required
  29. from models.dataset import ChildChunk, DocumentSegment
  30. from models.model import UploadFile
  31. from services.dataset_operation_log_service import DatasetOperationLogService
  32. from services.dataset_service import DatasetService, DocumentService, SegmentService
  33. from services.dataset_version_redis_service import RedisVersionService
  34. from services.entities.knowledge_entities.knowledge_entities import ChildChunkUpdateArgs, SegmentUpdateArgs
  35. from services.errors.chunk import ChildChunkDeleteIndexError as ChildChunkDeleteIndexServiceError
  36. from services.errors.chunk import ChildChunkIndexingError as ChildChunkIndexingServiceError
  37. from tasks.batch_create_segment_to_index_task import batch_create_segment_to_index_task
  38. class DatasetDocumentSegmentListApi(Resource):
  39. @setup_required
  40. @login_required
  41. @account_initialization_required
  42. def get(self, dataset_id, document_id):
  43. dataset_id = str(dataset_id)
  44. document_id = str(document_id)
  45. dataset = DatasetService.get_dataset(dataset_id)
  46. if not dataset:
  47. raise NotFound("Dataset not found.")
  48. try:
  49. DatasetService.check_dataset_permission(dataset, current_user)
  50. except services.errors.account.NoPermissionError as e:
  51. raise Forbidden(str(e))
  52. document = DocumentService.get_document(dataset_id, document_id)
  53. if not document:
  54. raise NotFound("Document not found.")
  55. parser = reqparse.RequestParser()
  56. parser.add_argument("limit", type=int, default=20, location="args")
  57. parser.add_argument("status", type=str, action="append", default=[], location="args")
  58. parser.add_argument("hit_count_gte", type=int, default=None, location="args")
  59. parser.add_argument("enabled", type=str, default="all", location="args")
  60. parser.add_argument("keyword", type=str, default=None, location="args")
  61. parser.add_argument("page", type=int, default=1, location="args")
  62. args = parser.parse_args()
  63. page = args["page"]
  64. limit = min(args["limit"], 100)
  65. status_list = args["status"]
  66. hit_count_gte = args["hit_count_gte"]
  67. keyword = args["keyword"]
  68. query = (
  69. select(DocumentSegment)
  70. .where(
  71. DocumentSegment.document_id == str(document_id),
  72. DocumentSegment.tenant_id == current_user.current_tenant_id,
  73. )
  74. .order_by(DocumentSegment.position.asc())
  75. )
  76. if status_list:
  77. query = query.where(DocumentSegment.status.in_(status_list))
  78. if hit_count_gte is not None:
  79. query = query.where(DocumentSegment.hit_count >= hit_count_gte)
  80. if keyword:
  81. query = query.where(DocumentSegment.content.ilike(f"%{keyword}%"))
  82. if args["enabled"].lower() != "all":
  83. if args["enabled"].lower() == "true":
  84. query = query.where(DocumentSegment.enabled == True)
  85. elif args["enabled"].lower() == "false":
  86. query = query.where(DocumentSegment.enabled == False)
  87. segments = db.paginate(select=query, page=page, per_page=limit, max_per_page=100, error_out=False)
  88. response = {
  89. "data": marshal(segments.items, segment_fields),
  90. "limit": limit,
  91. "total": segments.total,
  92. "total_pages": segments.pages,
  93. "page": page,
  94. }
  95. return response, 200
  96. @setup_required
  97. @login_required
  98. @account_initialization_required
  99. @cloud_edition_billing_rate_limit_check("knowledge")
  100. def delete(self, dataset_id, document_id):
  101. # check dataset
  102. dataset_id = str(dataset_id)
  103. dataset = DatasetService.get_dataset(dataset_id)
  104. if not dataset:
  105. raise NotFound("Dataset not found.")
  106. # check user's model setting
  107. DatasetService.check_dataset_model_setting(dataset)
  108. # check document
  109. document_id = str(document_id)
  110. document = DocumentService.get_document(dataset_id, document_id)
  111. if not document:
  112. raise NotFound("Document not found.")
  113. segment_ids = request.args.getlist("segment_id")
  114. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  115. if not current_user.is_dataset_editor:
  116. raise Forbidden()
  117. try:
  118. DatasetService.check_dataset_permission(dataset, current_user)
  119. except services.errors.account.NoPermissionError as e:
  120. raise Forbidden(str(e))
  121. SegmentService.delete_segments(segment_ids, document, dataset)
  122. return {"result": "success"}, 204
  123. class DatasetDocumentSegmentApi(Resource):
  124. @setup_required
  125. @login_required
  126. @account_initialization_required
  127. @cloud_edition_billing_resource_check("vector_space")
  128. @cloud_edition_billing_rate_limit_check("knowledge")
  129. def patch(self, dataset_id, document_id, action):
  130. dataset_id = str(dataset_id)
  131. dataset = DatasetService.get_dataset(dataset_id)
  132. if not dataset:
  133. raise NotFound("Dataset not found.")
  134. document_id = str(document_id)
  135. document = DocumentService.get_document(dataset_id, document_id)
  136. if not document:
  137. raise NotFound("Document not found.")
  138. # check user's model setting
  139. DatasetService.check_dataset_model_setting(dataset)
  140. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  141. if not current_user.is_dataset_editor:
  142. raise Forbidden()
  143. try:
  144. DatasetService.check_dataset_permission(dataset, current_user)
  145. except services.errors.account.NoPermissionError as e:
  146. raise Forbidden(str(e))
  147. if dataset.indexing_technique == "high_quality":
  148. # check embedding model setting
  149. try:
  150. model_manager = ModelManager()
  151. model_manager.get_model_instance(
  152. tenant_id=current_user.current_tenant_id,
  153. provider=dataset.embedding_model_provider,
  154. model_type=ModelType.TEXT_EMBEDDING,
  155. model=dataset.embedding_model,
  156. )
  157. except LLMBadRequestError:
  158. raise ProviderNotInitializeError(
  159. "No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
  160. )
  161. except ProviderTokenNotInitError as ex:
  162. raise ProviderNotInitializeError(ex.description)
  163. segment_ids = request.args.getlist("segment_id")
  164. document_indexing_cache_key = f"document_{document.id}_indexing"
  165. cache_result = redis_client.get(document_indexing_cache_key)
  166. if cache_result is not None:
  167. raise InvalidActionError("Document is being indexed, please try again later")
  168. try:
  169. SegmentService.update_segments_status(segment_ids, action, dataset, document)
  170. except Exception as e:
  171. raise InvalidActionError(str(e))
  172. return {"result": "success"}, 200
  173. class DatasetDocumentSegmentAddApi(Resource):
  174. @setup_required
  175. @login_required
  176. @account_initialization_required
  177. @cloud_edition_billing_resource_check("vector_space")
  178. @cloud_edition_billing_knowledge_limit_check("add_segment")
  179. @cloud_edition_billing_rate_limit_check("knowledge")
  180. def post(self, dataset_id, document_id):
  181. # check dataset
  182. dataset_id = str(dataset_id)
  183. dataset = DatasetService.get_dataset(dataset_id)
  184. if not dataset:
  185. raise NotFound("Dataset not found.")
  186. # check document
  187. document_id = str(document_id)
  188. document = DocumentService.get_document(dataset_id, document_id)
  189. if not document:
  190. raise NotFound("Document not found.")
  191. if not current_user.is_dataset_editor:
  192. raise Forbidden()
  193. # check embedding model setting
  194. if dataset.indexing_technique == "high_quality":
  195. try:
  196. model_manager = ModelManager()
  197. model_manager.get_model_instance(
  198. tenant_id=current_user.current_tenant_id,
  199. provider=dataset.embedding_model_provider,
  200. model_type=ModelType.TEXT_EMBEDDING,
  201. model=dataset.embedding_model,
  202. )
  203. except LLMBadRequestError:
  204. raise ProviderNotInitializeError(
  205. "No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
  206. )
  207. except ProviderTokenNotInitError as ex:
  208. raise ProviderNotInitializeError(ex.description)
  209. try:
  210. DatasetService.check_dataset_permission(dataset, current_user)
  211. except services.errors.account.NoPermissionError as e:
  212. raise Forbidden(str(e))
  213. # validate args
  214. parser = reqparse.RequestParser()
  215. parser.add_argument("content", type=str, required=True, nullable=False, location="json")
  216. parser.add_argument("answer", type=str, required=False, nullable=True, location="json")
  217. parser.add_argument("keywords", type=list, required=False, nullable=True, location="json")
  218. args = parser.parse_args()
  219. SegmentService.segment_create_args_validate(args, document)
  220. segment = SegmentService.create_segment(args, document, dataset)
  221. return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200
  222. class DatasetDocumentSegmentUpdateApi(Resource):
  223. @setup_required
  224. @login_required
  225. @account_initialization_required
  226. @cloud_edition_billing_resource_check("vector_space")
  227. @cloud_edition_billing_rate_limit_check("knowledge")
  228. def patch(self, dataset_id, document_id, segment_id):
  229. # check dataset
  230. dataset_id = str(dataset_id)
  231. dataset = DatasetService.get_dataset(dataset_id)
  232. if not dataset:
  233. raise NotFound("Dataset not found.")
  234. # check user's model setting
  235. DatasetService.check_dataset_model_setting(dataset)
  236. # check document
  237. document_id = str(document_id)
  238. document = DocumentService.get_document(dataset_id, document_id)
  239. if not document:
  240. raise NotFound("Document not found.")
  241. if dataset.indexing_technique == "high_quality":
  242. # check embedding model setting
  243. try:
  244. model_manager = ModelManager()
  245. model_manager.get_model_instance(
  246. tenant_id=current_user.current_tenant_id,
  247. provider=dataset.embedding_model_provider,
  248. model_type=ModelType.TEXT_EMBEDDING,
  249. model=dataset.embedding_model,
  250. )
  251. except LLMBadRequestError:
  252. raise ProviderNotInitializeError(
  253. "No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
  254. )
  255. except ProviderTokenNotInitError as ex:
  256. raise ProviderNotInitializeError(ex.description)
  257. # check segment
  258. segment_id = str(segment_id)
  259. segment = (
  260. db.session.query(DocumentSegment)
  261. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  262. .first()
  263. )
  264. if not segment:
  265. raise NotFound("Segment not found.")
  266. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  267. if not current_user.is_dataset_editor:
  268. raise Forbidden()
  269. try:
  270. DatasetService.check_dataset_permission(dataset, current_user)
  271. except services.errors.account.NoPermissionError as e:
  272. raise Forbidden(str(e))
  273. # validate args
  274. parser = reqparse.RequestParser()
  275. parser.add_argument("content", type=str, required=True, nullable=False, location="json")
  276. parser.add_argument("answer", type=str, required=False, nullable=True, location="json")
  277. parser.add_argument("keywords", type=list, required=False, nullable=True, location="json")
  278. parser.add_argument(
  279. "regenerate_child_chunks", type=bool, required=False, nullable=True, default=False, location="json"
  280. )
  281. args = parser.parse_args()
  282. SegmentService.segment_create_args_validate(args, document)
  283. segment = SegmentService.update_segment(SegmentUpdateArgs(**args), segment, document, dataset)
  284. return {"data": marshal(segment, segment_fields), "doc_form": document.doc_form}, 200
  285. @setup_required
  286. @login_required
  287. @account_initialization_required
  288. @cloud_edition_billing_rate_limit_check("knowledge")
  289. def delete(self, dataset_id, document_id, segment_id):
  290. # check dataset
  291. dataset_id = str(dataset_id)
  292. dataset = DatasetService.get_dataset(dataset_id)
  293. if not dataset:
  294. raise NotFound("Dataset not found.")
  295. # check user's model setting
  296. DatasetService.check_dataset_model_setting(dataset)
  297. # check document
  298. document_id = str(document_id)
  299. document = DocumentService.get_document(dataset_id, document_id)
  300. if not document:
  301. raise NotFound("Document not found.")
  302. # check segment
  303. segment_id = str(segment_id)
  304. segment = (
  305. db.session.query(DocumentSegment)
  306. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  307. .first()
  308. )
  309. if not segment:
  310. raise NotFound("Segment not found.")
  311. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  312. if not current_user.is_dataset_editor:
  313. raise Forbidden()
  314. try:
  315. DatasetService.check_dataset_permission(dataset, current_user)
  316. except services.errors.account.NoPermissionError as e:
  317. raise Forbidden(str(e))
  318. SegmentService.delete_segment(segment, document, dataset)
  319. return {"result": "success"}, 204
  320. class DatasetDocumentSegmentBatchImportApi(Resource):
  321. @setup_required
  322. @login_required
  323. @account_initialization_required
  324. @cloud_edition_billing_resource_check("vector_space")
  325. @cloud_edition_billing_knowledge_limit_check("add_segment")
  326. @cloud_edition_billing_rate_limit_check("knowledge")
  327. def post(self, dataset_id, document_id):
  328. # check dataset
  329. dataset_id = str(dataset_id)
  330. dataset = DatasetService.get_dataset(dataset_id)
  331. if not dataset:
  332. raise NotFound("Dataset not found.")
  333. # check document
  334. document_id = str(document_id)
  335. document = DocumentService.get_document(dataset_id, document_id)
  336. if not document:
  337. raise NotFound("Document not found.")
  338. parser = reqparse.RequestParser()
  339. parser.add_argument("upload_file_id", type=str, required=True, nullable=False, location="json")
  340. args = parser.parse_args()
  341. upload_file_id = args["upload_file_id"]
  342. upload_file = db.session.query(UploadFile).where(UploadFile.id == upload_file_id).first()
  343. if not upload_file:
  344. raise NotFound("UploadFile not found.")
  345. # check file type
  346. if not upload_file.name or not upload_file.name.lower().endswith(".csv"):
  347. raise ValueError("Invalid file type. Only CSV files are allowed")
  348. try:
  349. # async job
  350. job_id = str(uuid.uuid4())
  351. indexing_cache_key = f"segment_batch_import_{str(job_id)}"
  352. # send batch add segments task
  353. redis_client.setnx(indexing_cache_key, "waiting")
  354. batch_create_segment_to_index_task.delay(
  355. str(job_id), upload_file_id, dataset_id, document_id, current_user.current_tenant_id, current_user.id
  356. )
  357. except Exception as e:
  358. return {"error": str(e)}, 500
  359. return {"job_id": job_id, "job_status": "waiting"}, 200
  360. @setup_required
  361. @login_required
  362. @account_initialization_required
  363. def get(self, job_id):
  364. job_id = str(job_id)
  365. indexing_cache_key = f"segment_batch_import_{job_id}"
  366. cache_result = redis_client.get(indexing_cache_key)
  367. if cache_result is None:
  368. raise ValueError("The job does not exist.")
  369. return {"job_id": job_id, "job_status": cache_result.decode()}, 200
  370. class ChildChunkAddApi(Resource):
  371. @setup_required
  372. @login_required
  373. @account_initialization_required
  374. @cloud_edition_billing_resource_check("vector_space")
  375. @cloud_edition_billing_knowledge_limit_check("add_segment")
  376. @cloud_edition_billing_rate_limit_check("knowledge")
  377. def post(self, dataset_id, document_id, segment_id):
  378. # check dataset
  379. dataset_id = str(dataset_id)
  380. dataset = DatasetService.get_dataset(dataset_id)
  381. if not dataset:
  382. raise NotFound("Dataset not found.")
  383. # check document
  384. document_id = str(document_id)
  385. document = DocumentService.get_document(dataset_id, document_id)
  386. if not document:
  387. raise NotFound("Document not found.")
  388. # check segment
  389. segment_id = str(segment_id)
  390. segment = (
  391. db.session.query(DocumentSegment)
  392. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  393. .first()
  394. )
  395. if not segment:
  396. raise NotFound("Segment not found.")
  397. if not current_user.is_dataset_editor:
  398. raise Forbidden()
  399. # check embedding model setting
  400. if dataset.indexing_technique == "high_quality":
  401. try:
  402. model_manager = ModelManager()
  403. model_manager.get_model_instance(
  404. tenant_id=current_user.current_tenant_id,
  405. provider=dataset.embedding_model_provider,
  406. model_type=ModelType.TEXT_EMBEDDING,
  407. model=dataset.embedding_model,
  408. )
  409. except LLMBadRequestError:
  410. raise ProviderNotInitializeError(
  411. "No Embedding Model available. Please configure a valid provider in the Settings -> Model Provider."
  412. )
  413. except ProviderTokenNotInitError as ex:
  414. raise ProviderNotInitializeError(ex.description)
  415. try:
  416. DatasetService.check_dataset_permission(dataset, current_user)
  417. except services.errors.account.NoPermissionError as e:
  418. raise Forbidden(str(e))
  419. # validate args
  420. parser = reqparse.RequestParser()
  421. parser.add_argument("content", type=str, required=True, nullable=False, location="json")
  422. args = parser.parse_args()
  423. try:
  424. child_chunk = SegmentService.create_child_chunk(args.get("content"), segment, document, dataset)
  425. except ChildChunkIndexingServiceError as e:
  426. raise ChildChunkIndexingError(str(e))
  427. return {"data": marshal(child_chunk, child_chunk_fields)}, 200
  428. @setup_required
  429. @login_required
  430. @account_initialization_required
  431. def get(self, dataset_id, document_id, segment_id):
  432. # check dataset
  433. dataset_id = str(dataset_id)
  434. dataset = DatasetService.get_dataset(dataset_id)
  435. if not dataset:
  436. raise NotFound("Dataset not found.")
  437. # check user's model setting
  438. DatasetService.check_dataset_model_setting(dataset)
  439. # check document
  440. document_id = str(document_id)
  441. document = DocumentService.get_document(dataset_id, document_id)
  442. if not document:
  443. raise NotFound("Document not found.")
  444. # check segment
  445. segment_id = str(segment_id)
  446. segment = (
  447. db.session.query(DocumentSegment)
  448. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  449. .first()
  450. )
  451. if not segment:
  452. raise NotFound("Segment not found.")
  453. parser = reqparse.RequestParser()
  454. parser.add_argument("limit", type=int, default=20, location="args")
  455. parser.add_argument("keyword", type=str, default=None, location="args")
  456. parser.add_argument("page", type=int, default=1, location="args")
  457. args = parser.parse_args()
  458. page = args["page"]
  459. limit = min(args["limit"], 100)
  460. keyword = args["keyword"]
  461. child_chunks = SegmentService.get_child_chunks(segment_id, document_id, dataset_id, page, limit, keyword)
  462. return {
  463. "data": marshal(child_chunks.items, child_chunk_fields),
  464. "total": child_chunks.total,
  465. "total_pages": child_chunks.pages,
  466. "page": page,
  467. "limit": limit,
  468. }, 200
  469. @setup_required
  470. @login_required
  471. @account_initialization_required
  472. @cloud_edition_billing_resource_check("vector_space")
  473. @cloud_edition_billing_rate_limit_check("knowledge")
  474. def patch(self, dataset_id, document_id, segment_id):
  475. # check dataset
  476. dataset_id = str(dataset_id)
  477. dataset = DatasetService.get_dataset(dataset_id)
  478. if not dataset:
  479. raise NotFound("Dataset not found.")
  480. # check user's model setting
  481. DatasetService.check_dataset_model_setting(dataset)
  482. # check document
  483. document_id = str(document_id)
  484. document = DocumentService.get_document(dataset_id, document_id)
  485. if not document:
  486. raise NotFound("Document not found.")
  487. # check segment
  488. segment_id = str(segment_id)
  489. segment = (
  490. db.session.query(DocumentSegment)
  491. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  492. .first()
  493. )
  494. if not segment:
  495. raise NotFound("Segment not found.")
  496. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  497. if not current_user.is_dataset_editor:
  498. raise Forbidden()
  499. try:
  500. DatasetService.check_dataset_permission(dataset, current_user)
  501. except services.errors.account.NoPermissionError as e:
  502. raise Forbidden(str(e))
  503. # validate args
  504. parser = reqparse.RequestParser()
  505. parser.add_argument("chunks", type=list, required=True, nullable=False, location="json")
  506. args = parser.parse_args()
  507. try:
  508. chunks = [ChildChunkUpdateArgs(**chunk) for chunk in args.get("chunks")]
  509. child_chunks = SegmentService.update_child_chunks(chunks, segment, document, dataset)
  510. except ChildChunkIndexingServiceError as e:
  511. raise ChildChunkIndexingError(str(e))
  512. return {"data": marshal(child_chunks, child_chunk_fields)}, 200
  513. class ChildChunkUpdateApi(Resource):
  514. @setup_required
  515. @login_required
  516. @account_initialization_required
  517. @cloud_edition_billing_rate_limit_check("knowledge")
  518. def delete(self, dataset_id, document_id, segment_id, child_chunk_id):
  519. # check dataset
  520. dataset_id = str(dataset_id)
  521. dataset = DatasetService.get_dataset(dataset_id)
  522. if not dataset:
  523. raise NotFound("Dataset not found.")
  524. # check user's model setting
  525. DatasetService.check_dataset_model_setting(dataset)
  526. # check document
  527. document_id = str(document_id)
  528. document = DocumentService.get_document(dataset_id, document_id)
  529. if not document:
  530. raise NotFound("Document not found.")
  531. # check segment
  532. segment_id = str(segment_id)
  533. segment = (
  534. db.session.query(DocumentSegment)
  535. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  536. .first()
  537. )
  538. if not segment:
  539. raise NotFound("Segment not found.")
  540. # check child chunk
  541. child_chunk_id = str(child_chunk_id)
  542. child_chunk = (
  543. db.session.query(ChildChunk)
  544. .where(
  545. ChildChunk.id == str(child_chunk_id),
  546. ChildChunk.tenant_id == current_user.current_tenant_id,
  547. ChildChunk.segment_id == segment.id,
  548. ChildChunk.document_id == document_id,
  549. )
  550. .first()
  551. )
  552. if not child_chunk:
  553. raise NotFound("Child chunk not found.")
  554. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  555. if not current_user.is_dataset_editor:
  556. raise Forbidden()
  557. try:
  558. DatasetService.check_dataset_permission(dataset, current_user)
  559. except services.errors.account.NoPermissionError as e:
  560. raise Forbidden(str(e))
  561. try:
  562. SegmentService.delete_child_chunk(child_chunk, dataset)
  563. except ChildChunkDeleteIndexServiceError as e:
  564. raise ChildChunkDeleteIndexError(str(e))
  565. return {"result": "success"}, 204
  566. @setup_required
  567. @login_required
  568. @account_initialization_required
  569. @cloud_edition_billing_resource_check("vector_space")
  570. @cloud_edition_billing_rate_limit_check("knowledge")
  571. def patch(self, dataset_id, document_id, segment_id, child_chunk_id):
  572. # check dataset
  573. dataset_id = str(dataset_id)
  574. dataset = DatasetService.get_dataset(dataset_id)
  575. if not dataset:
  576. raise NotFound("Dataset not found.")
  577. # check user's model setting
  578. DatasetService.check_dataset_model_setting(dataset)
  579. # check document
  580. document_id = str(document_id)
  581. document = DocumentService.get_document(dataset_id, document_id)
  582. if not document:
  583. raise NotFound("Document not found.")
  584. # check segment
  585. segment_id = str(segment_id)
  586. segment = (
  587. db.session.query(DocumentSegment)
  588. .where(DocumentSegment.id == str(segment_id), DocumentSegment.tenant_id == current_user.current_tenant_id)
  589. .first()
  590. )
  591. if not segment:
  592. raise NotFound("Segment not found.")
  593. # check child chunk
  594. child_chunk_id = str(child_chunk_id)
  595. child_chunk = (
  596. db.session.query(ChildChunk)
  597. .where(
  598. ChildChunk.id == str(child_chunk_id),
  599. ChildChunk.tenant_id == current_user.current_tenant_id,
  600. ChildChunk.segment_id == segment.id,
  601. ChildChunk.document_id == document_id,
  602. )
  603. .first()
  604. )
  605. if not child_chunk:
  606. raise NotFound("Child chunk not found.")
  607. # The role of the current user in the ta table must be admin, owner, dataset_operator, or editor
  608. if not current_user.is_dataset_editor:
  609. raise Forbidden()
  610. try:
  611. DatasetService.check_dataset_permission(dataset, current_user)
  612. except services.errors.account.NoPermissionError as e:
  613. raise Forbidden(str(e))
  614. # validate args
  615. parser = reqparse.RequestParser()
  616. parser.add_argument("content", type=str, required=True, nullable=False, location="json")
  617. args = parser.parse_args()
  618. try:
  619. child_chunk = SegmentService.update_child_chunk(
  620. args.get("content"), child_chunk, segment, document, dataset
  621. )
  622. except ChildChunkIndexingServiceError as e:
  623. raise ChildChunkIndexingError(str(e))
  624. return {"data": marshal(child_chunk, child_chunk_fields)}, 200
  625. api.add_resource(DatasetDocumentSegmentListApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments")
  626. api.add_resource(
  627. DatasetDocumentSegmentApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment/<string:action>"
  628. )
  629. api.add_resource(DatasetDocumentSegmentAddApi, "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segment")
  630. api.add_resource(
  631. DatasetDocumentSegmentUpdateApi,
  632. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>",
  633. )
  634. api.add_resource(
  635. DatasetDocumentSegmentBatchImportApi,
  636. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/batch_import",
  637. "/datasets/batch_import_status/<uuid:job_id>",
  638. )
  639. api.add_resource(
  640. ChildChunkAddApi,
  641. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/child_chunks",
  642. )
  643. api.add_resource(
  644. ChildChunkUpdateApi,
  645. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/child_chunks/<uuid:child_chunk_id>",
  646. )
  647. # ============ Segment Version Control APIs ============
  648. class SegmentVersionListApi(Resource):
  649. """Segment version history list API"""
  650. @setup_required
  651. @login_required
  652. @account_initialization_required
  653. def get(self, dataset_id, document_id, segment_id):
  654. """Get segment version history"""
  655. dataset_id = str(dataset_id)
  656. document_id = str(document_id)
  657. segment_id = str(segment_id)
  658. # Check permissions
  659. dataset = DatasetService.get_dataset(dataset_id)
  660. if not dataset:
  661. raise NotFound("Dataset not found.")
  662. try:
  663. DatasetService.check_dataset_permission(dataset, current_user)
  664. except services.errors.account.NoPermissionError as e:
  665. raise Forbidden(str(e))
  666. document = DocumentService.get_document(dataset_id, document_id)
  667. if not document:
  668. raise NotFound("Document not found.")
  669. # Parse parameters
  670. parser = reqparse.RequestParser()
  671. parser.add_argument("page", type=int, default=1, location="args")
  672. parser.add_argument("per_page", type=int, default=20, location="args")
  673. parser.add_argument("include_content", type=bool, default=False, location="args")
  674. args = parser.parse_args()
  675. # Get versions
  676. result = RedisVersionService.get_segment_versions(
  677. segment_id=segment_id,
  678. page=args["page"],
  679. per_page=min(args["per_page"], 100),
  680. include_content=args["include_content"],
  681. )
  682. return result, 200
  683. class SegmentVersionDetailApi(Resource):
  684. """Segment version detail API"""
  685. @setup_required
  686. @login_required
  687. @account_initialization_required
  688. def get(self, dataset_id, document_id, segment_id, version_number):
  689. """Get detailed info for a specific version"""
  690. dataset_id = str(dataset_id)
  691. document_id = str(document_id)
  692. segment_id = str(segment_id)
  693. # Check permissions
  694. dataset = DatasetService.get_dataset(dataset_id)
  695. if not dataset:
  696. raise NotFound("Dataset not found.")
  697. try:
  698. DatasetService.check_dataset_permission(dataset, current_user)
  699. except services.errors.account.NoPermissionError as e:
  700. raise Forbidden(str(e))
  701. # Get version
  702. version = RedisVersionService.get_version_detail(segment_id=segment_id, version_number=version_number)
  703. if not version:
  704. raise NotFound("Version not found.")
  705. return version, 200
  706. class SegmentVersionCompareApi(Resource):
  707. """Version comparison API"""
  708. @setup_required
  709. @login_required
  710. @account_initialization_required
  711. def get(self, dataset_id, document_id, segment_id):
  712. """Compare two versions"""
  713. dataset_id = str(dataset_id)
  714. document_id = str(document_id)
  715. segment_id = str(segment_id)
  716. # Check permissions
  717. dataset = DatasetService.get_dataset(dataset_id)
  718. if not dataset:
  719. raise NotFound("Dataset not found.")
  720. try:
  721. DatasetService.check_dataset_permission(dataset, current_user)
  722. except services.errors.account.NoPermissionError as e:
  723. raise Forbidden(str(e))
  724. # Parse parameters
  725. parser = reqparse.RequestParser()
  726. parser.add_argument("v1", type=int, required=True, location="args")
  727. parser.add_argument("v2", type=int, required=True, location="args")
  728. args = parser.parse_args()
  729. # Compare versions
  730. comparison = RedisVersionService.compare_versions(
  731. segment_id=segment_id, version_number_1=args["v1"], version_number_2=args["v2"]
  732. )
  733. if not comparison:
  734. raise NotFound("One or both versions not found.")
  735. return comparison, 200
  736. class SegmentVersionStatisticsApi(Resource):
  737. """Version statistics API"""
  738. @setup_required
  739. @login_required
  740. @account_initialization_required
  741. def get(self, dataset_id, document_id, segment_id):
  742. """Get version statistics for a segment"""
  743. dataset_id = str(dataset_id)
  744. document_id = str(document_id)
  745. segment_id = str(segment_id)
  746. # Check permissions
  747. dataset = DatasetService.get_dataset(dataset_id)
  748. if not dataset:
  749. raise NotFound("Dataset not found.")
  750. try:
  751. DatasetService.check_dataset_permission(dataset, current_user)
  752. except services.errors.account.NoPermissionError as e:
  753. raise Forbidden(str(e))
  754. # Get statistics
  755. stats = RedisVersionService.get_segment_statistics(segment_id)
  756. return stats, 200
  757. class DatasetOperationLogApi(Resource):
  758. """Dataset operation log API"""
  759. @setup_required
  760. @login_required
  761. @account_initialization_required
  762. def get(self, dataset_id):
  763. """Get dataset operation logs"""
  764. dataset_id = str(dataset_id)
  765. # Check permissions
  766. dataset = DatasetService.get_dataset(dataset_id)
  767. if not dataset:
  768. raise NotFound("Dataset not found.")
  769. try:
  770. DatasetService.check_dataset_permission(dataset, current_user)
  771. except services.errors.account.NoPermissionError as e:
  772. raise Forbidden(str(e))
  773. # Parse parameters
  774. parser = reqparse.RequestParser()
  775. parser.add_argument("resource_type", type=str, location="args")
  776. parser.add_argument("resource_id", type=str, location="args")
  777. parser.add_argument("action", type=str, location="args")
  778. parser.add_argument("account_id", type=str, location="args")
  779. parser.add_argument("page", type=int, default=1, location="args")
  780. parser.add_argument("per_page", type=int, default=20, location="args")
  781. args = parser.parse_args()
  782. # Get logs
  783. result = DatasetOperationLogService.get_dataset_operation_logs(
  784. tenant_id=current_user.current_tenant_id,
  785. dataset_id=dataset_id,
  786. resource_type=args.get("resource_type"),
  787. resource_id=args.get("resource_id"),
  788. action=args.get("action"),
  789. account_id=args.get("account_id"),
  790. page=args["page"],
  791. per_page=min(args["per_page"], 100),
  792. )
  793. return result, 200
  794. # Register version control APIs
  795. api.add_resource(
  796. SegmentVersionListApi,
  797. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/versions",
  798. )
  799. api.add_resource(
  800. SegmentVersionDetailApi,
  801. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/versions/<int:version_number>",
  802. )
  803. api.add_resource(
  804. SegmentVersionCompareApi,
  805. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/versions/compare",
  806. )
  807. api.add_resource(
  808. SegmentVersionStatisticsApi,
  809. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/segments/<uuid:segment_id>/versions/statistics",
  810. )
  811. api.add_resource(DatasetOperationLogApi, "/datasets/<uuid:dataset_id>/operation-logs")