You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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. if data.get("permission") == "partial_members":
  294. part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
  295. data.update({"partial_member_list": part_users_list})
  296. # check embedding setting
  297. provider_manager = ProviderManager()
  298. assert isinstance(current_user, Account)
  299. cid = current_user.current_tenant_id
  300. assert cid is not None
  301. configurations = provider_manager.get_configurations(tenant_id=cid)
  302. embedding_models = configurations.get_models(model_type=ModelType.TEXT_EMBEDDING, only_active=True)
  303. model_names = []
  304. for embedding_model in embedding_models:
  305. model_names.append(f"{embedding_model.model}:{embedding_model.provider.provider}")
  306. if data["indexing_technique"] == "high_quality":
  307. item_model = f"{data['embedding_model']}:{data['embedding_model_provider']}"
  308. if item_model in model_names:
  309. data["embedding_available"] = True
  310. else:
  311. data["embedding_available"] = False
  312. else:
  313. data["embedding_available"] = True
  314. if data.get("permission") == "partial_members":
  315. part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
  316. data.update({"partial_member_list": part_users_list})
  317. return data, 200
  318. @service_api_ns.expect(dataset_update_parser)
  319. @service_api_ns.doc("update_dataset")
  320. @service_api_ns.doc(description="Update an existing dataset")
  321. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  322. @service_api_ns.doc(
  323. responses={
  324. 200: "Dataset updated successfully",
  325. 401: "Unauthorized - invalid API token",
  326. 403: "Forbidden - insufficient permissions",
  327. 404: "Dataset not found",
  328. }
  329. )
  330. @cloud_edition_billing_rate_limit_check("knowledge", "dataset")
  331. def patch(self, _, dataset_id):
  332. dataset_id_str = str(dataset_id)
  333. dataset = DatasetService.get_dataset(dataset_id_str)
  334. if dataset is None:
  335. raise NotFound("Dataset not found.")
  336. args = dataset_update_parser.parse_args()
  337. data = request.get_json()
  338. # check embedding model setting
  339. if data.get("indexing_technique") == "high_quality" or data.get("embedding_model_provider"):
  340. DatasetService.check_embedding_model_setting(
  341. dataset.tenant_id, data.get("embedding_model_provider"), data.get("embedding_model")
  342. )
  343. if (
  344. data.get("retrieval_model")
  345. and data.get("retrieval_model").get("reranking_model")
  346. and data.get("retrieval_model").get("reranking_model").get("reranking_provider_name")
  347. ):
  348. DatasetService.check_reranking_model_setting(
  349. dataset.tenant_id,
  350. data.get("retrieval_model").get("reranking_model").get("reranking_provider_name"),
  351. data.get("retrieval_model").get("reranking_model").get("reranking_model_name"),
  352. )
  353. # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
  354. DatasetPermissionService.check_permission(
  355. current_user, dataset, data.get("permission"), data.get("partial_member_list")
  356. )
  357. dataset = DatasetService.update_dataset(dataset_id_str, args, current_user)
  358. if dataset is None:
  359. raise NotFound("Dataset not found.")
  360. result_data = marshal(dataset, dataset_detail_fields)
  361. assert isinstance(current_user, Account)
  362. tenant_id = current_user.current_tenant_id
  363. if data.get("partial_member_list") and data.get("permission") == "partial_members":
  364. DatasetPermissionService.update_partial_member_list(
  365. tenant_id, dataset_id_str, data.get("partial_member_list")
  366. )
  367. # clear partial member list when permission is only_me or all_team_members
  368. elif (
  369. data.get("permission") == DatasetPermissionEnum.ONLY_ME
  370. or data.get("permission") == DatasetPermissionEnum.ALL_TEAM
  371. ):
  372. DatasetPermissionService.clear_partial_member_list(dataset_id_str)
  373. partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str)
  374. result_data.update({"partial_member_list": partial_member_list})
  375. return result_data, 200
  376. @service_api_ns.doc("delete_dataset")
  377. @service_api_ns.doc(description="Delete a dataset")
  378. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  379. @service_api_ns.doc(
  380. responses={
  381. 204: "Dataset deleted successfully",
  382. 401: "Unauthorized - invalid API token",
  383. 404: "Dataset not found",
  384. 409: "Conflict - dataset is in use",
  385. }
  386. )
  387. @cloud_edition_billing_rate_limit_check("knowledge", "dataset")
  388. def delete(self, _, dataset_id):
  389. """
  390. Deletes a dataset given its ID.
  391. Args:
  392. _: ignore
  393. dataset_id (UUID): The ID of the dataset to be deleted.
  394. Returns:
  395. dict: A dictionary with a key 'result' and a value 'success'
  396. if the dataset was successfully deleted. Omitted in HTTP response.
  397. int: HTTP status code 204 indicating that the operation was successful.
  398. Raises:
  399. NotFound: If the dataset with the given ID does not exist.
  400. """
  401. dataset_id_str = str(dataset_id)
  402. try:
  403. if DatasetService.delete_dataset(dataset_id_str, current_user):
  404. DatasetPermissionService.clear_partial_member_list(dataset_id_str)
  405. return 204
  406. else:
  407. raise NotFound("Dataset not found.")
  408. except services.errors.dataset.DatasetInUseError:
  409. raise DatasetInUseError()
  410. @service_api_ns.route("/datasets/<uuid:dataset_id>/documents/status/<string:action>")
  411. class DocumentStatusApi(DatasetApiResource):
  412. """Resource for batch document status operations."""
  413. @service_api_ns.doc("update_document_status")
  414. @service_api_ns.doc(description="Batch update document status")
  415. @service_api_ns.doc(
  416. params={
  417. "dataset_id": "Dataset ID",
  418. "action": "Action to perform: 'enable', 'disable', 'archive', or 'un_archive'",
  419. }
  420. )
  421. @service_api_ns.doc(
  422. responses={
  423. 200: "Document status updated successfully",
  424. 401: "Unauthorized - invalid API token",
  425. 403: "Forbidden - insufficient permissions",
  426. 404: "Dataset not found",
  427. 400: "Bad request - invalid action",
  428. }
  429. )
  430. def patch(self, tenant_id, dataset_id, action: Literal["enable", "disable", "archive", "un_archive"]):
  431. """
  432. Batch update document status.
  433. Args:
  434. tenant_id: tenant id
  435. dataset_id: dataset id
  436. action: action to perform (Literal["enable", "disable", "archive", "un_archive"])
  437. Returns:
  438. dict: A dictionary with a key 'result' and a value 'success'
  439. int: HTTP status code 200 indicating that the operation was successful.
  440. Raises:
  441. NotFound: If the dataset with the given ID does not exist.
  442. Forbidden: If the user does not have permission.
  443. InvalidActionError: If the action is invalid or cannot be performed.
  444. """
  445. dataset_id_str = str(dataset_id)
  446. dataset = DatasetService.get_dataset(dataset_id_str)
  447. if dataset is None:
  448. raise NotFound("Dataset not found.")
  449. # Check user's permission
  450. try:
  451. DatasetService.check_dataset_permission(dataset, current_user)
  452. except services.errors.account.NoPermissionError as e:
  453. raise Forbidden(str(e))
  454. # Check dataset model setting
  455. DatasetService.check_dataset_model_setting(dataset)
  456. # Get document IDs from request body
  457. data = request.get_json()
  458. document_ids = data.get("document_ids", [])
  459. try:
  460. DocumentService.batch_update_document_status(dataset, document_ids, action, current_user)
  461. except services.errors.document.DocumentIndexingError as e:
  462. raise InvalidActionError(str(e))
  463. except ValueError as e:
  464. raise InvalidActionError(str(e))
  465. return {"result": "success"}, 200
  466. @service_api_ns.route("/datasets/tags")
  467. class DatasetTagsApi(DatasetApiResource):
  468. @service_api_ns.doc("list_dataset_tags")
  469. @service_api_ns.doc(description="Get all knowledge type tags")
  470. @service_api_ns.doc(
  471. responses={
  472. 200: "Tags retrieved successfully",
  473. 401: "Unauthorized - invalid API token",
  474. }
  475. )
  476. @validate_dataset_token
  477. @service_api_ns.marshal_with(build_dataset_tag_fields(service_api_ns))
  478. def get(self, _, dataset_id):
  479. """Get all knowledge type tags."""
  480. assert isinstance(current_user, Account)
  481. cid = current_user.current_tenant_id
  482. assert cid is not None
  483. tags = TagService.get_tags("knowledge", cid)
  484. return tags, 200
  485. @service_api_ns.expect(tag_create_parser)
  486. @service_api_ns.doc("create_dataset_tag")
  487. @service_api_ns.doc(description="Add a knowledge type tag")
  488. @service_api_ns.doc(
  489. responses={
  490. 200: "Tag created successfully",
  491. 401: "Unauthorized - invalid API token",
  492. 403: "Forbidden - insufficient permissions",
  493. }
  494. )
  495. @service_api_ns.marshal_with(build_dataset_tag_fields(service_api_ns))
  496. @validate_dataset_token
  497. def post(self, _, dataset_id):
  498. """Add a knowledge type tag."""
  499. assert isinstance(current_user, Account)
  500. if not (current_user.is_editor or current_user.is_dataset_editor):
  501. raise Forbidden()
  502. args = tag_create_parser.parse_args()
  503. args["type"] = "knowledge"
  504. tag = TagService.save_tags(args)
  505. response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": 0}
  506. return response, 200
  507. @service_api_ns.expect(tag_update_parser)
  508. @service_api_ns.doc("update_dataset_tag")
  509. @service_api_ns.doc(description="Update a knowledge type tag")
  510. @service_api_ns.doc(
  511. responses={
  512. 200: "Tag updated successfully",
  513. 401: "Unauthorized - invalid API token",
  514. 403: "Forbidden - insufficient permissions",
  515. }
  516. )
  517. @service_api_ns.marshal_with(build_dataset_tag_fields(service_api_ns))
  518. @validate_dataset_token
  519. def patch(self, _, dataset_id):
  520. assert isinstance(current_user, Account)
  521. if not (current_user.is_editor or current_user.is_dataset_editor):
  522. raise Forbidden()
  523. args = tag_update_parser.parse_args()
  524. args["type"] = "knowledge"
  525. tag = TagService.update_tags(args, args.get("tag_id"))
  526. binding_count = TagService.get_tag_binding_count(args.get("tag_id"))
  527. response = {"id": tag.id, "name": tag.name, "type": tag.type, "binding_count": binding_count}
  528. return response, 200
  529. @service_api_ns.expect(tag_delete_parser)
  530. @service_api_ns.doc("delete_dataset_tag")
  531. @service_api_ns.doc(description="Delete a knowledge type tag")
  532. @service_api_ns.doc(
  533. responses={
  534. 204: "Tag deleted successfully",
  535. 401: "Unauthorized - invalid API token",
  536. 403: "Forbidden - insufficient permissions",
  537. }
  538. )
  539. @validate_dataset_token
  540. def delete(self, _, dataset_id):
  541. """Delete a knowledge type tag."""
  542. assert isinstance(current_user, Account)
  543. if not current_user.is_editor:
  544. raise Forbidden()
  545. args = tag_delete_parser.parse_args()
  546. TagService.delete_tag(args.get("tag_id"))
  547. return 204
  548. @service_api_ns.route("/datasets/tags/binding")
  549. class DatasetTagBindingApi(DatasetApiResource):
  550. @service_api_ns.expect(tag_binding_parser)
  551. @service_api_ns.doc("bind_dataset_tags")
  552. @service_api_ns.doc(description="Bind tags to a dataset")
  553. @service_api_ns.doc(
  554. responses={
  555. 204: "Tags bound successfully",
  556. 401: "Unauthorized - invalid API token",
  557. 403: "Forbidden - insufficient permissions",
  558. }
  559. )
  560. @validate_dataset_token
  561. def post(self, _, dataset_id):
  562. # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
  563. assert isinstance(current_user, Account)
  564. if not (current_user.is_editor or current_user.is_dataset_editor):
  565. raise Forbidden()
  566. args = tag_binding_parser.parse_args()
  567. args["type"] = "knowledge"
  568. TagService.save_tag_binding(args)
  569. return 204
  570. @service_api_ns.route("/datasets/tags/unbinding")
  571. class DatasetTagUnbindingApi(DatasetApiResource):
  572. @service_api_ns.expect(tag_unbinding_parser)
  573. @service_api_ns.doc("unbind_dataset_tag")
  574. @service_api_ns.doc(description="Unbind a tag from a dataset")
  575. @service_api_ns.doc(
  576. responses={
  577. 204: "Tag unbound successfully",
  578. 401: "Unauthorized - invalid API token",
  579. 403: "Forbidden - insufficient permissions",
  580. }
  581. )
  582. @validate_dataset_token
  583. def post(self, _, dataset_id):
  584. # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator
  585. assert isinstance(current_user, Account)
  586. if not (current_user.is_editor or current_user.is_dataset_editor):
  587. raise Forbidden()
  588. args = tag_unbinding_parser.parse_args()
  589. args["type"] = "knowledge"
  590. TagService.delete_tag_binding(args)
  591. return 204
  592. @service_api_ns.route("/datasets/<uuid:dataset_id>/tags")
  593. class DatasetTagsBindingStatusApi(DatasetApiResource):
  594. @service_api_ns.doc("get_dataset_tags_binding_status")
  595. @service_api_ns.doc(description="Get tags bound to a specific dataset")
  596. @service_api_ns.doc(params={"dataset_id": "Dataset ID"})
  597. @service_api_ns.doc(
  598. responses={
  599. 200: "Tags retrieved successfully",
  600. 401: "Unauthorized - invalid API token",
  601. }
  602. )
  603. @validate_dataset_token
  604. def get(self, _, *args, **kwargs):
  605. """Get all knowledge type tags."""
  606. dataset_id = kwargs.get("dataset_id")
  607. assert isinstance(current_user, Account)
  608. assert current_user.current_tenant_id is not None
  609. tags = TagService.get_tags_by_target_id("knowledge", current_user.current_tenant_id, str(dataset_id))
  610. tags_list = [{"id": tag.id, "name": tag.name} for tag in tags]
  611. response = {"data": tags_list, "total": len(tags)}
  612. return response, 200