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

annotation.py 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. from typing import Literal
  2. from flask import request
  3. from flask_restx import Api, Namespace, Resource, fields, reqparse
  4. from flask_restx.api import HTTPStatus
  5. from werkzeug.exceptions import Forbidden
  6. from controllers.service_api import service_api_ns
  7. from controllers.service_api.wraps import validate_app_token
  8. from extensions.ext_redis import redis_client
  9. from fields.annotation_fields import annotation_fields, build_annotation_model
  10. from libs.login import current_user
  11. from models.account import Account
  12. from models.model import App
  13. from services.annotation_service import AppAnnotationService
  14. # Define parsers for annotation API
  15. annotation_create_parser = reqparse.RequestParser()
  16. annotation_create_parser.add_argument("question", required=True, type=str, location="json", help="Annotation question")
  17. annotation_create_parser.add_argument("answer", required=True, type=str, location="json", help="Annotation answer")
  18. annotation_reply_action_parser = reqparse.RequestParser()
  19. annotation_reply_action_parser.add_argument(
  20. "score_threshold", required=True, type=float, location="json", help="Score threshold for annotation matching"
  21. )
  22. annotation_reply_action_parser.add_argument(
  23. "embedding_provider_name", required=True, type=str, location="json", help="Embedding provider name"
  24. )
  25. annotation_reply_action_parser.add_argument(
  26. "embedding_model_name", required=True, type=str, location="json", help="Embedding model name"
  27. )
  28. @service_api_ns.route("/apps/annotation-reply/<string:action>")
  29. class AnnotationReplyActionApi(Resource):
  30. @service_api_ns.expect(annotation_reply_action_parser)
  31. @service_api_ns.doc("annotation_reply_action")
  32. @service_api_ns.doc(description="Enable or disable annotation reply feature")
  33. @service_api_ns.doc(params={"action": "Action to perform: 'enable' or 'disable'"})
  34. @service_api_ns.doc(
  35. responses={
  36. 200: "Action completed successfully",
  37. 401: "Unauthorized - invalid API token",
  38. }
  39. )
  40. @validate_app_token
  41. def post(self, app_model: App, action: Literal["enable", "disable"]):
  42. """Enable or disable annotation reply feature."""
  43. args = annotation_reply_action_parser.parse_args()
  44. if action == "enable":
  45. result = AppAnnotationService.enable_app_annotation(args, app_model.id)
  46. elif action == "disable":
  47. result = AppAnnotationService.disable_app_annotation(app_model.id)
  48. return result, 200
  49. @service_api_ns.route("/apps/annotation-reply/<string:action>/status/<uuid:job_id>")
  50. class AnnotationReplyActionStatusApi(Resource):
  51. @service_api_ns.doc("get_annotation_reply_action_status")
  52. @service_api_ns.doc(description="Get the status of an annotation reply action job")
  53. @service_api_ns.doc(params={"action": "Action type", "job_id": "Job ID"})
  54. @service_api_ns.doc(
  55. responses={
  56. 200: "Job status retrieved successfully",
  57. 401: "Unauthorized - invalid API token",
  58. 404: "Job not found",
  59. }
  60. )
  61. @validate_app_token
  62. def get(self, app_model: App, job_id, action):
  63. """Get the status of an annotation reply action job."""
  64. job_id = str(job_id)
  65. app_annotation_job_key = f"{action}_app_annotation_job_{str(job_id)}"
  66. cache_result = redis_client.get(app_annotation_job_key)
  67. if cache_result is None:
  68. raise ValueError("The job does not exist.")
  69. job_status = cache_result.decode()
  70. error_msg = ""
  71. if job_status == "error":
  72. app_annotation_error_key = f"{action}_app_annotation_error_{str(job_id)}"
  73. error_msg = redis_client.get(app_annotation_error_key).decode()
  74. return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200
  75. # Define annotation list response model
  76. annotation_list_fields = {
  77. "data": fields.List(fields.Nested(annotation_fields)),
  78. "has_more": fields.Boolean,
  79. "limit": fields.Integer,
  80. "total": fields.Integer,
  81. "page": fields.Integer,
  82. }
  83. def build_annotation_list_model(api_or_ns: Api | Namespace):
  84. """Build the annotation list model for the API or Namespace."""
  85. copied_annotation_list_fields = annotation_list_fields.copy()
  86. copied_annotation_list_fields["data"] = fields.List(fields.Nested(build_annotation_model(api_or_ns)))
  87. return api_or_ns.model("AnnotationList", copied_annotation_list_fields)
  88. @service_api_ns.route("/apps/annotations")
  89. class AnnotationListApi(Resource):
  90. @service_api_ns.doc("list_annotations")
  91. @service_api_ns.doc(description="List annotations for the application")
  92. @service_api_ns.doc(
  93. responses={
  94. 200: "Annotations retrieved successfully",
  95. 401: "Unauthorized - invalid API token",
  96. }
  97. )
  98. @validate_app_token
  99. @service_api_ns.marshal_with(build_annotation_list_model(service_api_ns))
  100. def get(self, app_model: App):
  101. """List annotations for the application."""
  102. page = request.args.get("page", default=1, type=int)
  103. limit = request.args.get("limit", default=20, type=int)
  104. keyword = request.args.get("keyword", default="", type=str)
  105. annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_model.id, page, limit, keyword)
  106. return {
  107. "data": annotation_list,
  108. "has_more": len(annotation_list) == limit,
  109. "limit": limit,
  110. "total": total,
  111. "page": page,
  112. }
  113. @service_api_ns.expect(annotation_create_parser)
  114. @service_api_ns.doc("create_annotation")
  115. @service_api_ns.doc(description="Create a new annotation")
  116. @service_api_ns.doc(
  117. responses={
  118. 201: "Annotation created successfully",
  119. 401: "Unauthorized - invalid API token",
  120. }
  121. )
  122. @validate_app_token
  123. @service_api_ns.marshal_with(build_annotation_model(service_api_ns), code=HTTPStatus.CREATED)
  124. def post(self, app_model: App):
  125. """Create a new annotation."""
  126. args = annotation_create_parser.parse_args()
  127. annotation = AppAnnotationService.insert_app_annotation_directly(args, app_model.id)
  128. return annotation, 201
  129. @service_api_ns.route("/apps/annotations/<uuid:annotation_id>")
  130. class AnnotationUpdateDeleteApi(Resource):
  131. @service_api_ns.expect(annotation_create_parser)
  132. @service_api_ns.doc("update_annotation")
  133. @service_api_ns.doc(description="Update an existing annotation")
  134. @service_api_ns.doc(params={"annotation_id": "Annotation ID"})
  135. @service_api_ns.doc(
  136. responses={
  137. 200: "Annotation updated successfully",
  138. 401: "Unauthorized - invalid API token",
  139. 403: "Forbidden - insufficient permissions",
  140. 404: "Annotation not found",
  141. }
  142. )
  143. @validate_app_token
  144. @service_api_ns.marshal_with(build_annotation_model(service_api_ns))
  145. def put(self, app_model: App, annotation_id):
  146. """Update an existing annotation."""
  147. assert isinstance(current_user, Account)
  148. if not current_user.is_editor:
  149. raise Forbidden()
  150. annotation_id = str(annotation_id)
  151. args = annotation_create_parser.parse_args()
  152. annotation = AppAnnotationService.update_app_annotation_directly(args, app_model.id, annotation_id)
  153. return annotation
  154. @service_api_ns.doc("delete_annotation")
  155. @service_api_ns.doc(description="Delete an annotation")
  156. @service_api_ns.doc(params={"annotation_id": "Annotation ID"})
  157. @service_api_ns.doc(
  158. responses={
  159. 204: "Annotation deleted successfully",
  160. 401: "Unauthorized - invalid API token",
  161. 403: "Forbidden - insufficient permissions",
  162. 404: "Annotation not found",
  163. }
  164. )
  165. @validate_app_token
  166. def delete(self, app_model: App, annotation_id):
  167. """Delete an annotation."""
  168. assert isinstance(current_user, Account)
  169. if not current_user.is_editor:
  170. raise Forbidden()
  171. annotation_id = str(annotation_id)
  172. AppAnnotationService.delete_app_annotation(app_model.id, annotation_id)
  173. return {"result": "success"}, 204