From 8a7af9aa7b284d3b771ee943d3c42bd15dff470b Mon Sep 17 00:00:00 2001 From: liugddx Date: Sun, 26 Oct 2025 22:50:59 +0800 Subject: [PATCH 1/4] feat: enhance annotation API to support optional message_id and content fields --- api/controllers/console/app/annotation.py | 15 +++++--- api/controllers/console/app/message.py | 43 +---------------------- api/services/annotation_service.py | 29 +++++++++------ 3 files changed, 29 insertions(+), 58 deletions(-) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 932214058ac1eb..0b3f59f8fa7c6f 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -175,8 +175,10 @@ def get(self, app_id): 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 +195,14 @@ def post(self, app_id): 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=str, 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 7e0ae370ef3440..3f662789406f93 100644 --- a/api/controllers/console/app/message.py +++ b/api/controllers/console/app/message.py @@ -16,7 +16,6 @@ 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.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 @@ def post(self, app_model): 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 c0d26cdd278a55..fb112cff4142f3 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -32,41 +32,48 @@ def up_insert_app_annotation_from_message(cls, args: dict, app_id: str) -> Messa if not app: raise NotFound("App not found") + + answer = args.get("answer") or args.get("content") + if not answer: + 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, From 4e679f012cd199d25e019c7774d364b57b233813 Mon Sep 17 00:00:00 2001 From: Guangdong Liu <804167098@qq.com> Date: Sun, 26 Oct 2025 23:01:44 +0800 Subject: [PATCH 2/4] Update api/controllers/console/app/annotation.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/controllers/console/app/annotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index 0b3f59f8fa7c6f..fc88d4ed7d617a 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -195,7 +195,7 @@ def post(self, app_id): app_id = str(app_id) parser = ( reqparse.RequestParser() - .add_argument("message_id", required=False, 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") From 42b8acb1c1b2221cc1a9f0aa9cc96888b1e00720 Mon Sep 17 00:00:00 2001 From: Guangdong Liu <804167098@qq.com> Date: Sun, 26 Oct 2025 23:03:09 +0800 Subject: [PATCH 3/4] Update api/services/annotation_service.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- api/services/annotation_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index fb112cff4142f3..9258def907f601 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -34,7 +34,7 @@ def up_insert_app_annotation_from_message(cls, args: dict, app_id: str) -> Messa raise NotFound("App not found") answer = args.get("answer") or args.get("content") - if not answer: + if answer is None: raise ValueError("Either 'answer' or 'content' must be provided") if args.get("message_id"): From c02b2e706953004248ad3ca6e9d4036e26bcab6d Mon Sep 17 00:00:00 2001 From: liugddx Date: Sun, 26 Oct 2025 23:08:29 +0800 Subject: [PATCH 4/4] feat: enhance annotation API to support optional message_id and content fields --- api/controllers/console/app/annotation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/controllers/console/app/annotation.py b/api/controllers/console/app/annotation.py index fc88d4ed7d617a..23b6809aa5f452 100644 --- a/api/controllers/console/app/annotation.py +++ b/api/controllers/console/app/annotation.py @@ -16,6 +16,7 @@ annotation_fields, annotation_hit_history_fields, ) +from libs.helper import uuid_value from libs.login import login_required from services.annotation_service import AppAnnotationService