Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. from typing import Literal
  2. from flask import request
  3. from flask_restx import marshal, reqparse
  4. from werkzeug.exceptions import Forbidden, NotFound
  5. import services.dataset_service
  6. from controllers.service_api import service_api_ns
  7. from controllers.service_api.dataset.error import DatasetInUseError, DatasetNameDuplicateError, InvalidActionError
  8. from controllers.service_api.wraps import (
  9. DatasetApiResource,
  10. cloud_edition_billing_rate_limit_check,
  11. validate_dataset_token,
  12. )
  13. from core.model_runtime.entities.model_entities import ModelType
  14. from core.plugin.entities.plugin import ModelProviderID
  15. from core.provider_manager import ProviderManager
  16. from fields.dataset_fields import dataset_detail_fields
  17. from fields.tag_fields import build_dataset_tag_fields
  18. from libs.login import current_user
  19. from models.account import Account
  20. from models.dataset import Dataset, DatasetPermissionEnum
  21. from services.dataset_service import DatasetPermissionService, DatasetService, DocumentService
  22. from services.entities.knowledge_entities.knowledge_entities import RetrievalModel
  23. from services.tag_service import TagService
  24. def _validate_name(name):
  25. if not name or len(name) < 1 or len(name) > 40:
  26. raise ValueError("Name must be between 1 to 40 characters.")
  27. return name
  28. def _validate_description_length(description):
  29. if description and len(description) > 400:
  30. raise ValueError("Description cannot exceed 400 characters.")
  31. return description
  32. # Define parsers for dataset operations
  33. dataset_create_parser = reqparse.RequestParser()
  34. dataset_create_parser.add_argument(
  35. "name",
  36. nullable=False,
  37. required=True,
  38. help="type is required. Name must be between 1 to 40 characters.",
  39. type=_validate_name,
  40. )
  41. dataset_create_parser.add_argument(
  42. "description",
  43. type=_validate_description_length,
  44. nullable=True,
  45. required=False,
  46. default="",
  47. )
  48. dataset_create_parser.add_argument(
  49. "indexing_technique",
  50. type=str,
  51. location="json",
  52. choices=Dataset.INDEXING_TECHNIQUE_LIST,
  53. help="Invalid indexing technique.",
  54. )
  55. dataset_create_parser.add_argument(
  56. "permission",
  57. type=str,
  58. location="json",
  59. choices=(DatasetPermissionEnum.ONLY_ME, DatasetPermissionEnum.ALL_TEAM, DatasetPermissionEnum.PARTIAL_TEAM),
  60. help="Invalid permission.",
  61. required=False,
  62. nullable=False,
  63. )
  64. dataset_create_parser.add_argument(
  65. "external_knowledge_api_id",
  66. type=str,
  67. nullable=True,
  68. required=False,
  69. default="_validate_name",
  70. )
  71. dataset_create_parser.add_argument(
  72. "provider",
  73. type=str,
  74. nullable=True,
  75. required=False,
  76. default="vendor",
  77. )
  78. dataset_create_parser.add_argument(
  79. "external_knowledge_id",
  80. type=str,
  81. nullable=True,
  82. required=False,
  83. )
  84. dataset_create_parser.add_argument("retrieval_model", type=dict, required=False, nullable=True, location="json")
  85. dataset_create_parser.add_argument("embedding_model", type=str, required=False, nullable=True, location="json")
  86. dataset_create_parser.add_argument("embedding_model_provider", type=str, required=False, nullable=True, location="json")
  87. dataset_update_parser = reqparse.RequestParser()
  88. dataset_update_parser.add_argument(
  89. "name",
  90. nullable=False,
  91. help="type is required. Name must be between 1 to 40 characters.",
  92. type=_validate_name,
  93. )
  94. dataset_update_parser.add_argument(
  95. "description", location="json", store_missing=False, type=_validate_description_length
  96. )
  97. dataset_update_parser.add_argument(
  98. "indexing_technique",
  99. type=str,
  100. location="json",
  101. choices=Dataset.INDEXING_TECHNIQUE_LIST,
  102. nullable=True,
  103. help="Invalid indexing technique.",
  104. )
  105. dataset_update_parser.add_argument(
  106. "permission",
  107. type=str,
  108. location="json",
  109. choices=(DatasetPermissionEnum.ONLY_ME, DatasetPermissionEnum.ALL_TEAM, DatasetPermissionEnum.PARTIAL_TEAM),
  110. help="Invalid permission.",
  111. )
  112. dataset_update_parser.add_argument("embedding_model", type=str, location="json", help="Invalid embedding model.")
  113. dataset_update_parser.add_argument(
  114. "embedding_model_provider", type=str, location="json", help="Invalid embedding model provider."
  115. )
  116. dataset_update_parser.add_argument("retrieval_model", type=dict, location="json", help="Invalid retrieval model.")
  117. dataset_update_parser.add_argument("partial_member_list", type=list, location="json", help="Invalid parent user list.")
  118. dataset_update_parser.add_argument(
  119. "external_retrieval_model",
  120. type=dict,
  121. required=False,
  122. nullable=True,
  123. location="json",
  124. help="Invalid external retrieval model.",
  125. )
  126. dataset_update_parser.add_argument(
  127. "external_knowledge_id",
  128. type=str,
  129. required=False,
  130. nullable=True,
  131. location="json",
  132. help="Invalid external knowledge id.",
  133. )
  134. dataset_update_parser.add_argument(
  135. "external_knowledge_api_id",
  136. type=str,
  137. required=False,
  138. nullable=True,
  139. location="json",
  140. help="Invalid external knowledge api id.",
  141. )
  142. tag_create_parser = reqparse.RequestParser()
  143. tag_create_parser.add_argument(
  144. "name",
  145. nullable=False,
  146. required=True,
  147. help="Name must be between 1 to 50 characters.",
  148. type=lambda x: x
  149. if x and 1 <= len(x) <= 50
  150. else (_ for _ in ()).throw(ValueError("Name must be between 1 to 50 characters.")),
  151. )
  152. tag_update_parser = reqparse.RequestParser()
  153. tag_update_parser.add_argument(
  154. "name",
  155. nullable=False,
  156. required=True,
  157. help="Name must be between 1 to 50 characters.",
  158. type=lambda x: x
  159. if x and 1 <= len(x) <= 50
  160. else (_ for _ in ()).throw(ValueError("Name must be between 1 to 50 characters.")),
  161. )
  162. tag_update_parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str)
  163. tag_delete_parser = reqparse.RequestParser()
  164. tag_delete_parser.add_argument("tag_id", nullable=False, required=True, help="Id of a tag.", type=str)
  165. tag_binding_parser = reqparse.RequestParser()
  166. tag_binding_parser.add_argument(
  167. "tag_ids", type=list, nullable=False, required=True, location="json", help="Tag IDs is required."
  168. )
  169. tag_binding_parser.add_argument(
  170. "target_id", type=str, nullable=False, required=True, location="json", help="Target Dataset ID is required."
  171. )
  172. tag_unbinding_parser = reqparse.RequestParser()
  173. tag_unbinding_parser.add_argument("tag_id", type=str, nullable=False, required=True, help="Tag ID is required.")
  174. tag_unbinding_parser.add_argument("target_id", type=str, nullable=False, required=True, help="Target ID is required.")
  175. @service_api_ns.route("/datasets")
  176. class DatasetListApi(DatasetApiResource):
  177. """Resource for datasets."""
  178. @service_api_ns.doc("list_datasets")
  179. @service_api_ns.doc(description="List all datasets")
  180. @service_api_ns.doc(
  181. responses={
  182. 200: "Datasets retrieved successfully",
  183. 401: "Unauthorized - invalid API token",
  184. }
  185. )
  186. def get(self, tenant_id):
  187. """Resource for getting datasets."""
  188. page = request.args.get("page", default=1, type=int)
  189. limit = request.args.get("limit", default=20, type=int)
  190. # provider = request.args.get("provider", default="vendor")
  191. search = request.args.get("keyword", default=None, type=str)
  192. tag_ids = request.args.getlist("tag_ids")
  193. include_all = request.args.get("include_all", default="false").lower() == "true"
  194. datasets, total = DatasetService.get_datasets(
  195. page, limit, tenant_id, current_user, search, tag_ids, include_all
  196. )
  197. # check embedding setting
  198. provider_manager = ProviderManager()
  199. assert isinstance(current_user, Account)
  200. cid = current_user.current_tenant_id
  201. assert cid is not None
  202. configurations = provider_manager.get_configurations(tenant_id=cid)
  203. embedding_models = configurations.get_models(model_type=ModelType.TEXT_EMBEDDING, only_active=True)
  204. model_names = []
  205. for embedding_model in embedding_models:
  206. model_names.append(f"{embedding_model.model}:{embedding_model.provider.provider}")
  207. data = marshal(datasets, dataset_detail_fields)
  208. for item in data:
  209. if item["indexing_technique"] == "high_quality" and item["embedding_model_provider"]:
  210. item["embedding_model_provider"] = str(ModelProviderID(item["embedding_model_provider"]))
  211. item_model = f"{item['embedding_model']}:{item['embedding_model_provider']}"
  212. if item_model in model_names:
  213. item["embedding_available"] = True
  214. else:
  215. item["embedding_available"] = False
  216. else:
  217. item["embedding_available"] = True
  218. response = {"data": data, "has_more": len(datasets) == limit, "limit": limit, "total": total, "page": page}
  219. return response, 200
  220. @service_api_ns.expect(dataset_create_parser)
  221. @service_api_ns.doc("create_dataset")
  222. @service_api_ns.doc(description="Create a new dataset")
  223. @service_api_ns.doc(
  224. responses={
  225. 200: "Dataset created successfully",
  226. 401: "Unauthorized - invalid API token",
  227. 400: "Bad request - invalid parameters",
  228. }
  229. )
  230. @cloud_edition_billing_rate_limit_check("knowledge", "dataset")
  231. def post(self, tenant_id):
  232. """Resource for creating datasets."""
  233. args = dataset_create_parser.parse_args()
  234. if args.get("embedding_model_provider"):
  235. DatasetService.check_embedding_model_setting(
  236. tenant_id, args.get("embedding_model_provider"), args.get("embedding_model")
  237. )
  238. if (
  239. args.get("retrieval_model")
  240. and args.get("retrieval_model").get("reranking_model")
  241. and args.get("retrieval_model").get("reranking_model").get("reranking_provider_name")
  242. ):
  243. DatasetService.check_reranking_model_setting(
  244. tenant_id,
  245. args.get("retrieval_model").get("reranking_model").get("reranking_provider_name"),
  246. args.get("retrieval_model").get("reranking_model").get("reranking_model_name"),
  247. )
  248. try:
  249. assert isinstance(current_user, Account)
  250. dataset = DatasetService.create_empty_dataset(
  251. tenant_id=tenant_id,
  252. name=args["name"],
  253. description=args["description"],
  254. indexing_technique=args["indexing_technique"],
  255. account=current_user,
  256. permission=args["permission"],
  257. provider=args["provider"],
  258. external_knowledge_api_id=args["external_knowledge_api_id"],
  259. external_knowledge_id=args["external_knowledge_id"],
  260. embedding_model_provider=args["embedding_model_provider"],
  261. embedding_model_name=args["embedding_model"],
  262. retrieval_model=RetrievalModel(**args["retrieval_model"])
  263. if args["retrieval_model"] is not None
  264. else None,
  265. )
  266. except services.errors.dataset.DatasetNameDuplicateError:
  267. raise DatasetNameDuplicateError()
  268. return marshal(dataset, dataset_detail_fields), 200
  269. @service_api_ns.route("/datasets/<uuid:dataset_id>")
  270. class DatasetApi(DatasetApiResource):
  271. """Resource for dataset."""
  272. @service_api_ns.doc("get_dataset")
  273. @service_api_ns.doc(description="Get a specific dataset by ID")
  274. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  275. @service_api_ns.doc(
  276. responses={
  277. 200: "Dataset retrieved successfully",
  278. 401: "Unauthorized - invalid API token",
  279. 403: "Forbidden - insufficient permissions",
  280. 404: "Dataset not found",
  281. }
  282. )
  283. def get(self, _, dataset_id):
  284. dataset_id_str = str(dataset_id)
  285. dataset = DatasetService.get_dataset(dataset_id_str)
  286. if dataset is None:
  287. raise NotFound("Dataset not found.")
  288. try:
  289. DatasetService.check_dataset_permission(dataset, current_user)
  290. except services.errors.account.NoPermissionError as e:
  291. raise Forbidden(str(e))
  292. data = marshal(dataset, dataset_detail_fields)
  293. # check embedding setting
  294. provider_manager = ProviderManager()
  295. assert isinstance(current_user, Account)
  296. cid = current_user.current_tenant_id
  297. assert cid is not None
  298. configurations = provider_manager.get_configurations(tenant_id=cid)
  299. embedding_models = configurations.get_models(model_type=ModelType.TEXT_EMBEDDING, only_active=True)
  300. model_names = []
  301. for embedding_model in embedding_models:
  302. model_names.append(f"{embedding_model.model}:{embedding_model.provider.provider}")
  303. if data["indexing_technique"] == "high_quality":
  304. item_model = f"{data['embedding_model']}:{data['embedding_model_provider']}"
  305. if item_model in model_names:
  306. data["embedding_available"] = True
  307. else:
  308. data["embedding_available"] = False
  309. else:
  310. data["embedding_available"] = True
  311. if data.get("permission") == "partial_members":
  312. part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
  313. data.update({"partial_member_list": part_users_list})
  314. return data, 200
  315. @service_api_ns.expect(dataset_update_parser)
  316. @service_api_ns.doc("update_dataset")
  317. @service_api_ns.doc(description="Update an existing dataset")
  318. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  319. @service_api_ns.doc(
  320. responses={
  321. 200: "Dataset updated successfully",
  322. 401: "Unauthorized - invalid API token",
  323. 403: "Forbidden - insufficient permissions",
  324. 404: "Dataset not found",
  325. }
  326. )
  327. @cloud_edition_billing_rate_limit_check("knowledge", "dataset")
  328. def patch(self, _, dataset_id):
  329. dataset_id_str = str(dataset_id)
  330. dataset = DatasetService.get_dataset(dataset_id_str)
  331. if dataset is None:
  332. raise NotFound("Dataset not found.")
  333. args = dataset_update_parser.parse_args()
  334. data = request.get_json()
  335. # check embedding model setting
  336. if data.get("indexing_technique") == "high_quality" or data.get("embedding_model_provider"):
  337. DatasetService.check_embedding_model_setting(
  338. dataset.tenant_id, data.get("embedding_model_provider"), data.get("embedding_model")
  339. )
  340. if (
  341. data.get("retrieval_model")
  342. and data.get("retrieval_model").get("reranking_model")
  343. and data.get("retrieval_model").get("reranking_model").get("reranking_provider_name")
  344. ):
  345. DatasetService.check_reranking_model_setting(
  346. dataset.tenant_id,
  347. data.get("retrieval_model").get("reranking_model").get("reranking_provider_name"),
  348. data.get("retrieval_model").get("reranking_model").get("reranking_model_name"),
  349. )
  350. # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
  351. DatasetPermissionService.check_permission(
  352. current_user, dataset, data.get("permission"), data.get("partial_member_list")
  353. )
  354. dataset = DatasetService.update_dataset(dataset_id_str, args, current_user)
  355. if dataset is None:
  356. raise NotFound("Dataset not found.")
  357. result_data = marshal(dataset, dataset_detail_fields)
  358. assert isinstance(current_user, Account)
  359. tenant_id = current_user.current_tenant_id
  360. if data.get("partial_member_list") and data.get("permission") == "partial_members":
  361. DatasetPermissionService.update_partial_member_list(
  362. tenant_id, dataset_id_str, data.get("partial_member_list")
  363. )
  364. # clear partial member list when permission is only_me or all_team_members
  365. elif (
  366. data.get("permission") == DatasetPermissionEnum.ONLY_ME
  367. or data.get("permission") == DatasetPermissionEnum.ALL_TEAM
  368. ):
  369. DatasetPermissionService.clear_partial_member_list(dataset_id_str)
  370. partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
  371. result_data.update({"partial_member_list": partial_member_list})
  372. return result_data, 200
  373. @service_api_ns.doc("delete_dataset")
  374. @service_api_ns.doc(description="Delete a dataset")
  375. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  376. @service_api_ns.doc(
  377. responses={
  378. 204: "Dataset deleted successfully",
  379. 401: "Unauthorized - invalid API token",
  380. 404: "Dataset not found",
  381. 409: "Conflict - dataset is in use",
  382. }
  383. )
  384. @cloud_edition_billing_rate_limit_check("knowledge", "dataset")
  385. def delete(self, _, dataset_id):
  386. """
  387. Deletes a dataset given its ID.
  388. Args:
  389. _: ignore
  390. dataset_id (UUID): The ID of the dataset to be deleted.
  391. Returns:
  392. dict: A dictionary with a key 'result' and a value 'success'
  393. if the dataset was successfully deleted. Omitted in HTTP response.
  394. int: HTTP status code 204 indicating that the operation was successful.
  395. Raises:
  396. NotFound: If the dataset with the given ID does not exist.
  397. """
  398. dataset_id_str = str(dataset_id)
  399. try:
  400. if DatasetService.delete_dataset(dataset_id_str, current_user):
  401. DatasetPermissionService.clear_partial_member_list(dataset_id_str)
  402. return 204
  403. else:
  404. raise NotFound("Dataset not found.")
  405. except services.errors.dataset.DatasetInUseError:
  406. raise DatasetInUseError()
  407. @service_api_ns.route("/datasets/<uuid:dataset_id>/documents/status/<string:action>")
  408. class DocumentStatusApi(DatasetApiResource):
  409. """Resource for batch document status operations."""
  410. @service_api_ns.doc("update_document_status")
  411. @service_api_ns.doc(description="Batch update document status")
  412. @service_api_ns.doc(
  413. params={
  414. "dataset_id": "Dataset ID",
  415. "action": "Action to perform: 'enable', 'disable', 'archive', or 'un_archive'",
  416. }
  417. )
  418. @service_api_ns.doc(
  419. responses={
  420. 200: "Document status updated successfully",
  421. 401: "Unauthorized - invalid API token",
  422. 403: "Forbidden - insufficient permissions",
  423. 404: "Dataset not found",
  424. 400: "Bad request - invalid action",
  425. }
  426. )
  427. def patch(self, tenant_id, dataset_id, action: Literal["enable", "disable", "archive", "un_archive"]):
  428. """
  429. Batch update document status.
  430. Args:
  431. tenant_id: tenant id
  432. dataset_id: dataset id
  433. action: action to perform (Literal["enable", "disable", "archive", "un_archive"])
  434. Returns:
  435. dict: A dictionary with a key 'result' and a value 'success'
  436. int: HTTP status code 200 indicating that the operation was successful.
  437. Raises:
  438. NotFound: If the dataset with the given ID does not exist.
  439. Forbidden: If the user does not have permission.
  440. InvalidActionError: If the action is invalid or cannot be performed.
  441. """
  442. dataset_id_str = str(dataset_id)
  443. dataset = DatasetService.get_dataset(dataset_id_str)
  444. if dataset is None:
  445. raise NotFound("Dataset not found.")
  446. # Check user's permission
  447. try:
  448. DatasetService.check_dataset_permission(dataset, current_user)
  449. except services.errors.account.NoPermissionError as e:
  450. raise Forbidden(str(e))
  451. # Check dataset model setting
  452. DatasetService.check_dataset_model_setting(dataset)
  453. # Get document IDs from request body
  454. data = request.get_json()
  455. document_ids = data.get("document_ids", [])
  456. try:
  457. DocumentService.batch_update_document_status(dataset, document_ids, action, current_user)
  458. except services.errors.document.DocumentIndexingError as e:
  459. raise InvalidActionError(str(e))
  460. except ValueError as e:
  461. raise InvalidActionError(str(e))
  462. return {"result": "success"}, 200
  463. @service_api_ns.route("/datasets/tags")
  464. class DatasetTagsApi(DatasetApiResource):
  465. @service_api_ns.doc("list_dataset_tags")
  466. @service_api_ns.doc(description="Get all knowledge type tags")
  467. @service_api_ns.doc(
  468. responses={
  469. 200: "Tags retrieved successfully",
  470. 401: "Unauthorized - invalid API token",
  471. }
  472. )
  473. @validate_dataset_token
  474. @service_api_ns.marshal_with(build_dataset_tag_fields(service_api_ns))
  475. def get(self, _, dataset_id):
  476. """Get all knowledge type tags."""
  477. assert isinstance(current_user, Account)
  478. cid = current_user.current_tenant_id
  479. assert cid is not None
  480. tags = TagService.get_tags("knowledge", cid)
  481. return tags, 200
  482. @service_api_ns.expect(tag_create_parser)
  483. @service_api_ns.doc("create_dataset_tag")
  484. @service_api_ns.doc(description="Add a knowledge type tag")
  485. @service_api_ns.doc(
  486. responses={
  487. 200: "Tag created successfully",
  488. 401: "Unauthorized - invalid API token",
  489. 403: "Forbidden - insufficient permissions",
  490. }
  491. )
  492. @service_api_ns.marshal_with(build_dataset_tag_fields(service_api_ns))
  493. @validate_dataset_token
  494. def post(self, _, dataset_id):
  495. """Add a knowledge type tag."""
  496. assert isinstance(current_user, Account)
  497. if not (current_user.has_edit_permission or current_user.is_dataset_editor):
  498. raise Forbidden()
  499. args = tag_create_parser.parse_args()
  500. args["type"] = "knowledge"
  501. tag = TagService.save_tags(args)
  502. response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": 0}
  503. return response, 200
  504. @service_api_ns.expect(tag_update_parser)
  505. @service_api_ns.doc("update_dataset_tag")
  506. @service_api_ns.doc(description="Update a knowledge type tag")
  507. @service_api_ns.doc(
  508. responses={
  509. 200: "Tag updated successfully",
  510. 401: "Unauthorized - invalid API token",
  511. 403: "Forbidden - insufficient permissions",
  512. }
  513. )
  514. @service_api_ns.marshal_with(build_dataset_tag_fields(service_api_ns))
  515. @validate_dataset_token
  516. def patch(self, _, dataset_id):
  517. assert isinstance(current_user, Account)
  518. if not (current_user.has_edit_permission or current_user.is_dataset_editor):
  519. raise Forbidden()
  520. args = tag_update_parser.parse_args()
  521. args["type"] = "knowledge"
  522. tag = TagService.update_tags(args, args.get("tag_id"))
  523. binding_count = TagService.get_tag_binding_count(args.get("tag_id"))
  524. response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": binding_count}
  525. return response, 200
  526. @service_api_ns.expect(tag_delete_parser)
  527. @service_api_ns.doc("delete_dataset_tag")
  528. @service_api_ns.doc(description="Delete a knowledge type tag")
  529. @service_api_ns.doc(
  530. responses={
  531. 204: "Tag deleted successfully",
  532. 401: "Unauthorized - invalid API token",
  533. 403: "Forbidden - insufficient permissions",
  534. }
  535. )
  536. @validate_dataset_token
  537. def delete(self, _, dataset_id):
  538. """Delete a knowledge type tag."""
  539. assert isinstance(current_user, Account)
  540. if not current_user.has_edit_permission:
  541. raise Forbidden()
  542. args = tag_delete_parser.parse_args()
  543. TagService.delete_tag(args.get("tag_id"))
  544. return 204
  545. @service_api_ns.route("/datasets/tags/binding")
  546. class DatasetTagBindingApi(DatasetApiResource):
  547. @service_api_ns.expect(tag_binding_parser)
  548. @service_api_ns.doc("bind_dataset_tags")
  549. @service_api_ns.doc(description="Bind tags to a dataset")
  550. @service_api_ns.doc(
  551. responses={
  552. 204: "Tags bound successfully",
  553. 401: "Unauthorized - invalid API token",
  554. 403: "Forbidden - insufficient permissions",
  555. }
  556. )
  557. @validate_dataset_token
  558. def post(self, _, dataset_id):
  559. # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
  560. assert isinstance(current_user, Account)
  561. if not (current_user.has_edit_permission or current_user.is_dataset_editor):
  562. raise Forbidden()
  563. args = tag_binding_parser.parse_args()
  564. args["type"] = "knowledge"
  565. TagService.save_tag_binding(args)
  566. return 204
  567. @service_api_ns.route("/datasets/tags/unbinding")
  568. class DatasetTagUnbindingApi(DatasetApiResource):
  569. @service_api_ns.expect(tag_unbinding_parser)
  570. @service_api_ns.doc("unbind_dataset_tag")
  571. @service_api_ns.doc(description="Unbind a tag from a dataset")
  572. @service_api_ns.doc(
  573. responses={
  574. 204: "Tag unbound successfully",
  575. 401: "Unauthorized - invalid API token",
  576. 403: "Forbidden - insufficient permissions",
  577. }
  578. )
  579. @validate_dataset_token
  580. def post(self, _, dataset_id):
  581. # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
  582. assert isinstance(current_user, Account)
  583. if not (current_user.has_edit_permission or current_user.is_dataset_editor):
  584. raise Forbidden()
  585. args = tag_unbinding_parser.parse_args()
  586. args["type"] = "knowledge"
  587. TagService.delete_tag_binding(args)
  588. return 204
  589. @service_api_ns.route("/datasets/<uuid:dataset_id>/tags")
  590. class DatasetTagsBindingStatusApi(DatasetApiResource):
  591. @service_api_ns.doc("get_dataset_tags_binding_status")
  592. @service_api_ns.doc(description="Get tags bound to a specific dataset")
  593. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  594. @service_api_ns.doc(
  595. responses={
  596. 200: "Tags retrieved successfully",
  597. 401: "Unauthorized - invalid API token",
  598. }
  599. )
  600. @validate_dataset_token
  601. def get(self, _, *args, **kwargs):
  602. """Get all knowledge type tags."""
  603. dataset_id = kwargs.get("dataset_id")
  604. assert isinstance(current_user, Account)
  605. assert current_user.current_tenant_id is not None
  606. tags = TagService.get_tags_by_target_id("knowledge", current_user.current_tenant_id, str(dataset_id))
  607. tags_list = [{"id": tag.id, "name": tag.name} for tag in tags]
  608. response = {"data": tags_list, "total": len(tags)}
  609. return response, 200