Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

external.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. from flask import request
  2. from flask_login import current_user
  3. from flask_restx import Resource, fields, marshal, reqparse
  4. from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
  5. import services
  6. from controllers.console import api, console_ns
  7. from controllers.console.datasets.error import DatasetNameDuplicateError
  8. from controllers.console.wraps import account_initialization_required, setup_required
  9. from fields.dataset_fields import dataset_detail_fields
  10. from libs.login import login_required
  11. from services.dataset_service import DatasetService
  12. from services.external_knowledge_service import ExternalDatasetService
  13. from services.hit_testing_service import HitTestingService
  14. from services.knowledge_service import ExternalDatasetTestService
  15. def _validate_name(name):
  16. if not name or len(name) < 1 or len(name) > 100:
  17. raise ValueError("Name must be between 1 to 100 characters.")
  18. return name
  19. @console_ns.route("/datasets/external-knowledge-api")
  20. class ExternalApiTemplateListApi(Resource):
  21. @api.doc("get_external_api_templates")
  22. @api.doc(description="Get external knowledge API templates")
  23. @api.doc(
  24. params={
  25. "page": "Page number (default: 1)",
  26. "limit": "Number of items per page (default: 20)",
  27. "keyword": "Search keyword",
  28. }
  29. )
  30. @api.response(200, "External API templates retrieved successfully")
  31. @setup_required
  32. @login_required
  33. @account_initialization_required
  34. def get(self):
  35. page = request.args.get("page", default=1, type=int)
  36. limit = request.args.get("limit", default=20, type=int)
  37. search = request.args.get("keyword", default=None, type=str)
  38. external_knowledge_apis, total = ExternalDatasetService.get_external_knowledge_apis(
  39. page, limit, current_user.current_tenant_id, search
  40. )
  41. response = {
  42. "data": [item.to_dict() for item in external_knowledge_apis],
  43. "has_more": len(external_knowledge_apis) == limit,
  44. "limit": limit,
  45. "total": total,
  46. "page": page,
  47. }
  48. return response, 200
  49. @setup_required
  50. @login_required
  51. @account_initialization_required
  52. def post(self):
  53. parser = reqparse.RequestParser()
  54. parser.add_argument(
  55. "name",
  56. nullable=False,
  57. required=True,
  58. help="Name is required. Name must be between 1 to 100 characters.",
  59. type=_validate_name,
  60. )
  61. parser.add_argument(
  62. "settings",
  63. type=dict,
  64. location="json",
  65. nullable=False,
  66. required=True,
  67. )
  68. args = parser.parse_args()
  69. ExternalDatasetService.validate_api_list(args["settings"])
  70. # The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
  71. if not current_user.is_dataset_editor:
  72. raise Forbidden()
  73. try:
  74. external_knowledge_api = ExternalDatasetService.create_external_knowledge_api(
  75. tenant_id=current_user.current_tenant_id, user_id=current_user.id, args=args
  76. )
  77. except services.errors.dataset.DatasetNameDuplicateError:
  78. raise DatasetNameDuplicateError()
  79. return external_knowledge_api.to_dict(), 201
  80. @console_ns.route("/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>")
  81. class ExternalApiTemplateApi(Resource):
  82. @api.doc("get_external_api_template")
  83. @api.doc(description="Get external knowledge API template details")
  84. @api.doc(params={"external_knowledge_api_id": "External knowledge API ID"})
  85. @api.response(200, "External API template retrieved successfully")
  86. @api.response(404, "Template not found")
  87. @setup_required
  88. @login_required
  89. @account_initialization_required
  90. def get(self, external_knowledge_api_id):
  91. external_knowledge_api_id = str(external_knowledge_api_id)
  92. external_knowledge_api = ExternalDatasetService.get_external_knowledge_api(external_knowledge_api_id)
  93. if external_knowledge_api is None:
  94. raise NotFound("API template not found.")
  95. return external_knowledge_api.to_dict(), 200
  96. @setup_required
  97. @login_required
  98. @account_initialization_required
  99. def patch(self, external_knowledge_api_id):
  100. external_knowledge_api_id = str(external_knowledge_api_id)
  101. parser = reqparse.RequestParser()
  102. parser.add_argument(
  103. "name",
  104. nullable=False,
  105. required=True,
  106. help="type is required. Name must be between 1 to 100 characters.",
  107. type=_validate_name,
  108. )
  109. parser.add_argument(
  110. "settings",
  111. type=dict,
  112. location="json",
  113. nullable=False,
  114. required=True,
  115. )
  116. args = parser.parse_args()
  117. ExternalDatasetService.validate_api_list(args["settings"])
  118. external_knowledge_api = ExternalDatasetService.update_external_knowledge_api(
  119. tenant_id=current_user.current_tenant_id,
  120. user_id=current_user.id,
  121. external_knowledge_api_id=external_knowledge_api_id,
  122. args=args,
  123. )
  124. return external_knowledge_api.to_dict(), 200
  125. @setup_required
  126. @login_required
  127. @account_initialization_required
  128. def delete(self, external_knowledge_api_id):
  129. external_knowledge_api_id = str(external_knowledge_api_id)
  130. # The role of the current user in the ta table must be admin, owner, or editor
  131. if not current_user.is_editor or current_user.is_dataset_operator:
  132. raise Forbidden()
  133. ExternalDatasetService.delete_external_knowledge_api(current_user.current_tenant_id, external_knowledge_api_id)
  134. return {"result": "success"}, 204
  135. @console_ns.route("/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>/use-check")
  136. class ExternalApiUseCheckApi(Resource):
  137. @api.doc("check_external_api_usage")
  138. @api.doc(description="Check if external knowledge API is being used")
  139. @api.doc(params={"external_knowledge_api_id": "External knowledge API ID"})
  140. @api.response(200, "Usage check completed successfully")
  141. @setup_required
  142. @login_required
  143. @account_initialization_required
  144. def get(self, external_knowledge_api_id):
  145. external_knowledge_api_id = str(external_knowledge_api_id)
  146. external_knowledge_api_is_using, count = ExternalDatasetService.external_knowledge_api_use_check(
  147. external_knowledge_api_id
  148. )
  149. return {"is_using": external_knowledge_api_is_using, "count": count}, 200
  150. @console_ns.route("/datasets/external")
  151. class ExternalDatasetCreateApi(Resource):
  152. @api.doc("create_external_dataset")
  153. @api.doc(description="Create external knowledge dataset")
  154. @api.expect(
  155. api.model(
  156. "CreateExternalDatasetRequest",
  157. {
  158. "external_knowledge_api_id": fields.String(required=True, description="External knowledge API ID"),
  159. "external_knowledge_id": fields.String(required=True, description="External knowledge ID"),
  160. "name": fields.String(required=True, description="Dataset name"),
  161. "description": fields.String(description="Dataset description"),
  162. },
  163. )
  164. )
  165. @api.response(201, "External dataset created successfully", dataset_detail_fields)
  166. @api.response(400, "Invalid parameters")
  167. @api.response(403, "Permission denied")
  168. @setup_required
  169. @login_required
  170. @account_initialization_required
  171. def post(self):
  172. # The role of the current user in the ta table must be admin, owner, or editor
  173. if not current_user.is_editor:
  174. raise Forbidden()
  175. parser = reqparse.RequestParser()
  176. parser.add_argument("external_knowledge_api_id", type=str, required=True, nullable=False, location="json")
  177. parser.add_argument("external_knowledge_id", type=str, required=True, nullable=False, location="json")
  178. parser.add_argument(
  179. "name",
  180. nullable=False,
  181. required=True,
  182. help="name is required. Name must be between 1 to 100 characters.",
  183. type=_validate_name,
  184. )
  185. parser.add_argument("description", type=str, required=False, nullable=True, location="json")
  186. parser.add_argument("external_retrieval_model", type=dict, required=False, location="json")
  187. args = parser.parse_args()
  188. # The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
  189. if not current_user.is_dataset_editor:
  190. raise Forbidden()
  191. try:
  192. dataset = ExternalDatasetService.create_external_dataset(
  193. tenant_id=current_user.current_tenant_id,
  194. user_id=current_user.id,
  195. args=args,
  196. )
  197. except services.errors.dataset.DatasetNameDuplicateError:
  198. raise DatasetNameDuplicateError()
  199. return marshal(dataset, dataset_detail_fields), 201
  200. @console_ns.route("/datasets/<uuid:dataset_id>/external-hit-testing")
  201. class ExternalKnowledgeHitTestingApi(Resource):
  202. @api.doc("test_external_knowledge_retrieval")
  203. @api.doc(description="Test external knowledge retrieval for dataset")
  204. @api.doc(params={"dataset_id": "Dataset ID"})
  205. @api.expect(
  206. api.model(
  207. "ExternalHitTestingRequest",
  208. {
  209. "query": fields.String(required=True, description="Query text for testing"),
  210. "retrieval_model": fields.Raw(description="Retrieval model configuration"),
  211. "external_retrieval_model": fields.Raw(description="External retrieval model configuration"),
  212. },
  213. )
  214. )
  215. @api.response(200, "External hit testing completed successfully")
  216. @api.response(404, "Dataset not found")
  217. @api.response(400, "Invalid parameters")
  218. @setup_required
  219. @login_required
  220. @account_initialization_required
  221. def post(self, dataset_id):
  222. dataset_id_str = str(dataset_id)
  223. dataset = DatasetService.get_dataset(dataset_id_str)
  224. if dataset is None:
  225. raise NotFound("Dataset not found.")
  226. try:
  227. DatasetService.check_dataset_permission(dataset, current_user)
  228. except services.errors.account.NoPermissionError as e:
  229. raise Forbidden(str(e))
  230. parser = reqparse.RequestParser()
  231. parser.add_argument("query", type=str, location="json")
  232. parser.add_argument("external_retrieval_model", type=dict, required=False, location="json")
  233. parser.add_argument("metadata_filtering_conditions", type=dict, required=False, location="json")
  234. args = parser.parse_args()
  235. HitTestingService.hit_testing_args_check(args)
  236. try:
  237. response = HitTestingService.external_retrieve(
  238. dataset=dataset,
  239. query=args["query"],
  240. account=current_user,
  241. external_retrieval_model=args["external_retrieval_model"],
  242. metadata_filtering_conditions=args["metadata_filtering_conditions"],
  243. )
  244. return response
  245. except Exception as e:
  246. raise InternalServerError(str(e))
  247. @console_ns.route("/test/retrieval")
  248. class BedrockRetrievalApi(Resource):
  249. # this api is only for internal testing
  250. @api.doc("bedrock_retrieval_test")
  251. @api.doc(description="Bedrock retrieval test (internal use only)")
  252. @api.expect(
  253. api.model(
  254. "BedrockRetrievalTestRequest",
  255. {
  256. "retrieval_setting": fields.Raw(required=True, description="Retrieval settings"),
  257. "query": fields.String(required=True, description="Query text"),
  258. "knowledge_id": fields.String(required=True, description="Knowledge ID"),
  259. },
  260. )
  261. )
  262. @api.response(200, "Bedrock retrieval test completed")
  263. def post(self):
  264. parser = reqparse.RequestParser()
  265. parser.add_argument("retrieval_setting", nullable=False, required=True, type=dict, location="json")
  266. parser.add_argument(
  267. "query",
  268. nullable=False,
  269. required=True,
  270. type=str,
  271. )
  272. parser.add_argument("knowledge_id", nullable=False, required=True, type=str)
  273. args = parser.parse_args()
  274. # Call the knowledge retrieval service
  275. result = ExternalDatasetTestService.knowledge_retrieval(
  276. args["retrieval_setting"], args["query"], args["knowledge_id"]
  277. )
  278. return result, 200