您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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