From a58ced8081d8a8e2acf4a13fd2d428eb608c2eb5 Mon Sep 17 00:00:00 2001 From: Guangdong Liu Date: Tue, 4 Nov 2025 14:11:09 +0800 Subject: [PATCH] feat: enhance annotation API to support optional message_id and content fields (#27460) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/controllers/console/app/annotation.py | 16 ++++++--- api/controllers/console/app/message.py | 43 +---------------------- api/services/annotation_service.py | 29 +++++++++------ 3 files changed, 30 insertions(+), 58 deletions(-) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 932214058a..23b6809aa5 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -16,6 +16,7 @@ from fields.annotation_fields import ( annotation_fields, annotation_hit_history_fields, ) +from libs.helper import uuid_value from libs.login import login_required from services.annotation_service import AppAnnotationService @@ -175,8 +176,10 @@ class AnnotationApi(Resource): api.model( "CreateAnnotationRequest", { - "question": fields.String(required=True, description="Question text"), - "answer": fields.String(required=True, description="Answer text"), + "message_id": fields.String(description="Message ID (optional)"), + "question": fields.String(description="Question text (required when message_id not provided)"), + "answer": fields.String(description="Answer text (use 'answer' or 'content')"), + "content": fields.String(description="Content text (use 'answer' or 'content')"), "annotation_reply": fields.Raw(description="Annotation reply data"), }, ) @@ -193,11 +196,14 @@ class AnnotationApi(Resource): app_id = str(app_id) parser = ( reqparse.RequestParser() - .add_argument("question", required=True, type=str, location="json") - .add_argument("answer", required=True, type=str, location="json") + .add_argument("message_id", required=False, type=uuid_value, location="json") + .add_argument("question", required=False, type=str, location="json") + .add_argument("answer", required=False, type=str, location="json") + .add_argument("content", required=False, type=str, location="json") + .add_argument("annotation_reply", required=False, type=dict, location="json") ) args = parser.parse_args() - annotation = AppAnnotationService.insert_app_annotation_directly(args, app_id) + annotation = AppAnnotationService.up_insert_app_annotation_from_message(args, app_id) return annotation @setup_required diff --git a/api/controllers/console/app/message.py b/api/controllers/console/app/message.py index 7e0ae370ef..3f66278940 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -16,7 +16,6 @@ from controllers.console.app.wraps import get_app_model from controllers.console.explore.error import AppSuggestedQuestionsAfterAnswerDisabledError from controllers.console.wraps import ( account_initialization_required, - cloud_edition_billing_resource_check, edit_permission_required, setup_required, ) @@ -24,12 +23,11 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from core.model_runtime.errors.invoke import InvokeError from extensions.ext_database import db -from fields.conversation_fields import annotation_fields, message_detail_fields +from fields.conversation_fields import message_detail_fields from libs.helper import uuid_value from libs.infinite_scroll_pagination import InfiniteScrollPagination from libs.login import current_account_with_tenant, login_required from models.model import AppMode, Conversation, Message, MessageAnnotation, MessageFeedback -from services.annotation_service import AppAnnotationService from services.errors.conversation import ConversationNotExistsError from services.errors.message import MessageNotExistsError, SuggestedQuestionsAfterAnswerDisabledError from services.message_service import MessageService @@ -194,45 +192,6 @@ class MessageFeedbackApi(Resource): return {"result": "success"} -@console_ns.route("/apps//annotations") -class MessageAnnotationApi(Resource): - @api.doc("create_message_annotation") - @api.doc(description="Create message annotation") - @api.doc(params={"app_id": "Application ID"}) - @api.expect( - api.model( - "MessageAnnotationRequest", - { - "message_id": fields.String(description="Message ID"), - "question": fields.String(required=True, description="Question text"), - "answer": fields.String(required=True, description="Answer text"), - "annotation_reply": fields.Raw(description="Annotation reply"), - }, - ) - ) - @api.response(200, "Annotation created successfully", annotation_fields) - @api.response(403, "Insufficient permissions") - @marshal_with(annotation_fields) - @get_app_model - @setup_required - @login_required - @cloud_edition_billing_resource_check("annotation") - @account_initialization_required - @edit_permission_required - def post(self, app_model): - parser = ( - reqparse.RequestParser() - .add_argument("message_id", required=False, type=uuid_value, location="json") - .add_argument("question", required=True, type=str, location="json") - .add_argument("answer", required=True, type=str, location="json") - .add_argument("annotation_reply", required=False, type=dict, location="json") - ) - args = parser.parse_args() - annotation = AppAnnotationService.up_insert_app_annotation_from_message(args, app_model.id) - - return annotation - - @console_ns.route("/apps//annotations/count") class MessageAnnotationCountApi(Resource): @api.doc("get_annotation_count") diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index c0d26cdd27..9258def907 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -32,41 +32,48 @@ class AppAnnotationService: if not app: raise NotFound("App not found") + + answer = args.get("answer") or args.get("content") + if answer is None: + raise ValueError("Either 'answer' or 'content' must be provided") + if args.get("message_id"): message_id = str(args["message_id"]) - # get message info message = db.session.query(Message).where(Message.id == message_id, Message.app_id == app.id).first() if not message: raise NotFound("Message Not Exists.") + question = args.get("question") or message.query or "" + annotation: MessageAnnotation | None = message.annotation - # save the message annotation if annotation: - annotation.content = args["answer"] - annotation.question = args["question"] + annotation.content = answer + annotation.question = question else: annotation = MessageAnnotation( app_id=app.id, conversation_id=message.conversation_id, message_id=message.id, - content=args["answer"], - question=args["question"], + content=answer, + question=question, account_id=current_user.id, ) else: - annotation = MessageAnnotation( - app_id=app.id, content=args["answer"], question=args["question"], account_id=current_user.id - ) + question = args.get("question") + if not question: + raise ValueError("'question' is required when 'message_id' is not provided") + + annotation = MessageAnnotation(app_id=app.id, content=answer, question=question, account_id=current_user.id) db.session.add(annotation) db.session.commit() - # if annotation reply is enabled , add annotation to index + annotation_setting = db.session.query(AppAnnotationSetting).where(AppAnnotationSetting.app_id == app_id).first() assert current_tenant_id is not None if annotation_setting: add_annotation_to_index_task.delay( annotation.id, - args["question"], + annotation.question, current_tenant_id, app_id, annotation_setting.collection_binding_id,