From a47b514fe844f93cb8f1e89773795c399a607928 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 31 Dec 2024 01:00:27 +0800 Subject: [PATCH 1/8] feat(improve-api-endpoints): Added Datasets and Annotation APIs - Added AnnotationReplyActionApi, AnnotationListApi, AnnotationUpdateDeleteApi - Added GET and PATCH endpoints to DatasetApi - Applied current_user in validate_app_token just like what it is done in validate_dataset_token Related to: #11727 --- api/controllers/service_api/__init__.py | 2 +- api/controllers/service_api/app/annotation.py | 94 +++++++++++ .../service_api/dataset/dataset.py | 150 +++++++++++++++++- api/controllers/service_api/wraps.py | 25 ++- 4 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 api/controllers/service_api/app/annotation.py diff --git a/api/controllers/service_api/__init__.py b/api/controllers/service_api/__init__.py index d6ab96c329f335..56b713bf047972 100644 --- a/api/controllers/service_api/__init__.py +++ b/api/controllers/service_api/__init__.py @@ -6,5 +6,5 @@ api = ExternalApi(bp) from . import index -from .app import app, audio, completion, conversation, file, message, workflow +from .app import annotation, app, audio, completion, conversation, file, message, workflow from .dataset import dataset, document, hit_testing, segment diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py new file mode 100644 index 00000000000000..92921791ae4cb3 --- /dev/null +++ b/api/controllers/service_api/app/annotation.py @@ -0,0 +1,94 @@ + +from flask import request +from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore +from werkzeug.exceptions import Forbidden + +from controllers.service_api import api +from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token +from fields.annotation_fields import ( + annotation_fields, +) +from libs.login import current_user +from models.model import App, EndUser +from services.annotation_service import AppAnnotationService + + +class AnnotationReplyActionApi(Resource): + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) + def post(self, app_model: App, end_user: EndUser, app_id, action): + app_id = str(app_id) + parser = reqparse.RequestParser() + parser.add_argument("score_threshold", required=True, type=float, location="json") + parser.add_argument("embedding_provider_name", required=True, type=str, location="json") + parser.add_argument("embedding_model_name", required=True, type=str, location="json") + args = parser.parse_args() + if action == "enable": + result = AppAnnotationService.enable_app_annotation(args, app_id) + elif action == "disable": + result = AppAnnotationService.disable_app_annotation(app_id) + else: + raise ValueError("Unsupported annotation reply action") + return result, 200 + + +class AnnotationListApi(Resource): + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) + def get(self, app_model: App, end_user: EndUser, app_id): + page = request.args.get("page", default=1, type=int) + limit = request.args.get("limit", default=20, type=int) + keyword = request.args.get("keyword", default="", type=str) + + app_id = str(app_id) + annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_id, page, limit, keyword) + response = { + "data": marshal(annotation_list, annotation_fields), + "has_more": len(annotation_list) == limit, + "limit": limit, + "total": total, + "page": page, + } + return response, 200 + + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) + @marshal_with(annotation_fields) + def post(self, app_model: App, end_user: EndUser, app_id): + print(current_user) + app_id = str(app_id) + parser = reqparse.RequestParser() + parser.add_argument("question", required=True, type=str, location="json") + parser.add_argument("answer", required=True, type=str, location="json") + args = parser.parse_args() + annotation = AppAnnotationService.insert_app_annotation_directly(args, app_id) + return annotation + + +class AnnotationUpdateDeleteApi(Resource): + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) + @marshal_with(annotation_fields) + def post(self, app_model: App, end_user: EndUser, app_id, annotation_id): + if not current_user.is_editor: + raise Forbidden() + + app_id = str(app_id) + annotation_id = str(annotation_id) + parser = reqparse.RequestParser() + parser.add_argument("question", required=True, type=str, location="json") + parser.add_argument("answer", required=True, type=str, location="json") + args = parser.parse_args() + annotation = AppAnnotationService.update_app_annotation_directly(args, app_id, annotation_id) + return annotation + + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) + def delete(self, app_model: App, end_user: EndUser, app_id, annotation_id): + if not current_user.is_editor: + raise Forbidden() + + app_id = str(app_id) + annotation_id = str(annotation_id) + AppAnnotationService.delete_app_annotation(app_id, annotation_id) + return {"result": "success"}, 200 + + +api.add_resource(AnnotationReplyActionApi, "/apps//annotation-reply/") +api.add_resource(AnnotationListApi, "/apps//annotations") +api.add_resource(AnnotationUpdateDeleteApi, "/apps//annotations/") \ No newline at end of file diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index d6a3beb6b80b9d..0b5e5b2130bf5f 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -1,6 +1,6 @@ from flask import request from flask_restful import marshal, reqparse # type: ignore -from werkzeug.exceptions import NotFound +from werkzeug.exceptions import Forbidden, NotFound import services.dataset_service from controllers.service_api import api @@ -11,7 +11,7 @@ from fields.dataset_fields import dataset_detail_fields from libs.login import current_user from models.dataset import Dataset, DatasetPermissionEnum -from services.dataset_service import DatasetService +from services.dataset_service import DatasetPermissionService, DatasetService def _validate_name(name): @@ -20,6 +20,12 @@ def _validate_name(name): return name +def _validate_description_length(description): + if len(description) > 400: + raise ValueError("Description cannot exceed 400 characters.") + return description + + class DatasetListApi(DatasetApiResource): """Resource for datasets.""" @@ -132,6 +138,145 @@ def post(self, tenant_id): class DatasetApi(DatasetApiResource): """Resource for dataset.""" + def get(self, _, dataset_id): + dataset_id_str = str(dataset_id) + dataset = DatasetService.get_dataset(dataset_id_str) + if dataset is None: + raise NotFound("Dataset not found.") + try: + DatasetService.check_dataset_permission(dataset, current_user) + except services.errors.account.NoPermissionError as e: + raise Forbidden(str(e)) + data = marshal(dataset, dataset_detail_fields) + if data.get("permission") == "partial_members": + part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + data.update({"partial_member_list": part_users_list}) + + # check embedding setting + provider_manager = ProviderManager() + configurations = provider_manager.get_configurations(tenant_id=current_user.current_tenant_id) + + embedding_models = configurations.get_models(model_type=ModelType.TEXT_EMBEDDING, only_active=True) + + model_names = [] + for embedding_model in embedding_models: + model_names.append(f"{embedding_model.model}:{embedding_model.provider.provider}") + + if data["indexing_technique"] == "high_quality": + item_model = f"{data['embedding_model']}:{data['embedding_model_provider']}" + if item_model in model_names: + data["embedding_available"] = True + else: + data["embedding_available"] = False + else: + data["embedding_available"] = True + + if data.get("permission") == "partial_members": + part_users_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + data.update({"partial_member_list": part_users_list}) + + return data, 200 + + def patch(self, _, dataset_id): + dataset_id_str = str(dataset_id) + dataset = DatasetService.get_dataset(dataset_id_str) + if dataset is None: + raise NotFound("Dataset not found.") + + parser = reqparse.RequestParser() + parser.add_argument( + "name", + nullable=False, + help="type is required. Name must be between 1 to 40 characters.", + type=_validate_name, + ) + parser.add_argument("description", location="json", store_missing=False, type=_validate_description_length) + parser.add_argument( + "indexing_technique", + type=str, + location="json", + choices=Dataset.INDEXING_TECHNIQUE_LIST, + nullable=True, + help="Invalid indexing technique.", + ) + parser.add_argument( + "permission", + type=str, + location="json", + choices=(DatasetPermissionEnum.ONLY_ME, DatasetPermissionEnum.ALL_TEAM, DatasetPermissionEnum.PARTIAL_TEAM), + help="Invalid permission.", + ) + parser.add_argument("embedding_model", type=str, location="json", help="Invalid embedding model.") + parser.add_argument( + "embedding_model_provider", type=str, location="json", help="Invalid embedding model provider." + ) + parser.add_argument("retrieval_model", type=dict, location="json", help="Invalid retrieval model.") + parser.add_argument("partial_member_list", type=list, location="json", help="Invalid parent user list.") + + parser.add_argument( + "external_retrieval_model", + type=dict, + required=False, + nullable=True, + location="json", + help="Invalid external retrieval model.", + ) + + parser.add_argument( + "external_knowledge_id", + type=str, + required=False, + nullable=True, + location="json", + help="Invalid external knowledge id.", + ) + + parser.add_argument( + "external_knowledge_api_id", + type=str, + required=False, + nullable=True, + location="json", + help="Invalid external knowledge api id.", + ) + args = parser.parse_args() + data = request.get_json() + + # check embedding model setting + if data.get("indexing_technique") == "high_quality": + DatasetService.check_embedding_model_setting( + dataset.tenant_id, data.get("embedding_model_provider"), data.get("embedding_model") + ) + + # The role of the current user in the ta table must be admin, owner, editor, or dataset_operator + DatasetPermissionService.check_permission( + current_user, dataset, data.get("permission"), data.get("partial_member_list") + ) + + dataset = DatasetService.update_dataset(dataset_id_str, args, current_user) + + if dataset is None: + raise NotFound("Dataset not found.") + + result_data = marshal(dataset, dataset_detail_fields) + tenant_id = current_user.current_tenant_id + + if data.get("partial_member_list") and data.get("permission") == "partial_members": + DatasetPermissionService.update_partial_member_list( + tenant_id, dataset_id_str, data.get("partial_member_list") + ) + # clear partial member list when permission is only_me or all_team_members + elif ( + data.get("permission") == DatasetPermissionEnum.ONLY_ME + or data.get("permission") == DatasetPermissionEnum.ALL_TEAM + ): + DatasetPermissionService.clear_partial_member_list(dataset_id_str) + + partial_member_list = DatasetPermissionService.get_dataset_partial_member_list(dataset_id_str) + result_data.update({"partial_member_list": partial_member_list}) + + return result_data, 200 + def delete(self, _, dataset_id): """ Deletes a dataset given its ID. @@ -152,6 +297,7 @@ def delete(self, _, dataset_id): try: if DatasetService.delete_dataset(dataset_id_str, current_user): + DatasetPermissionService.clear_partial_member_list(dataset_id_str) return {"result": "success"}, 204 else: raise NotFound("Dataset not found.") diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index 740b92ef8e4faf..70891a58d64e1d 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -54,6 +54,27 @@ def decorated_view(*args, **kwargs): if tenant.status == TenantStatus.ARCHIVE: raise Forbidden("The workspace's status is archived.") + tenant_account_join = ( + db.session.query(Tenant, TenantAccountJoin) + .filter(Tenant.id == api_token.tenant_id) + .filter(TenantAccountJoin.tenant_id == Tenant.id) + .filter(TenantAccountJoin.role.in_(["owner"])) + .filter(Tenant.status == TenantStatus.NORMAL) + .one_or_none() + ) # TODO: only owner information is required, so only one is returned. + if tenant_account_join: + tenant, ta = tenant_account_join + account = Account.query.filter_by(id=ta.account_id).first() + # Login admin + if account: + account.current_tenant = tenant + current_app.login_manager._update_request_context_with_user(account) # type: ignore + user_logged_in.send(current_app._get_current_object(), user=_get_user()) # type: ignore + else: + raise Unauthorized("Tenant owner account does not exist.") + else: + raise Unauthorized("Tenant does not exist.") + kwargs["app_model"] = app_model if fetch_user_arg: @@ -193,7 +214,7 @@ def validate_and_get_api_token(scope=None): .filter( ApiToken.token == auth_token, ApiToken.type == scope, - ) + ) .first() ) @@ -220,7 +241,7 @@ def create_or_update_end_user_for_user_id(app_model: App, user_id: Optional[str] EndUser.app_id == app_model.id, EndUser.session_id == user_id, EndUser.type == "service_api", - ) + ) .first() ) From d7854a5afdf7cc4b9697cbc2f4640151daadf0da Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 31 Dec 2024 13:41:16 +0800 Subject: [PATCH 2/8] feat(improve-api-endpoints): Added available model API This API endpoint shares the same API token with Dataset API. It is essential for patching the knowledge base in terms of embedding model. Related to: #11727 --- api/controllers/service_api/__init__.py | 1 + .../service_api/workspace/models.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 api/controllers/service_api/workspace/models.py diff --git a/api/controllers/service_api/__init__.py b/api/controllers/service_api/__init__.py index 56b713bf047972..1f562c5221cf4a 100644 --- a/api/controllers/service_api/__init__.py +++ b/api/controllers/service_api/__init__.py @@ -8,3 +8,4 @@ from . import index from .app import annotation, app, audio, completion, conversation, file, message, workflow from .dataset import dataset, document, hit_testing, segment +from .workspace import models diff --git a/api/controllers/service_api/workspace/models.py b/api/controllers/service_api/workspace/models.py new file mode 100644 index 00000000000000..61439bc287a5cd --- /dev/null +++ b/api/controllers/service_api/workspace/models.py @@ -0,0 +1,22 @@ +import logging + +from flask_login import current_user # type: ignore +from flask_restful import Resource, reqparse # type: ignore +from werkzeug.exceptions import Forbidden + +from controllers.service_api import api +from controllers.service_api.wraps import validate_dataset_token +from core.model_runtime.utils.encoders import jsonable_encoder +from services.model_provider_service import ModelProviderService + +class ModelProviderAvailableModelApi(Resource): + @validate_dataset_token + def get(self, _, model_type): + tenant_id = current_user.current_tenant_id + + model_provider_service = ModelProviderService() + models = model_provider_service.get_models_by_model_type(tenant_id=tenant_id, model_type=model_type) + + return jsonable_encoder({"data": models}) + +api.add_resource(ModelProviderAvailableModelApi, "/workspaces/current/models/model-types/") \ No newline at end of file From f9c86a1e06cba75f9d8866af7edd7f394842b6b0 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 31 Dec 2024 15:07:23 +0800 Subject: [PATCH 3/8] feat(improve-api-endpoints): Improve annotation API Because app token is assigned to a specific app, the validate_app_token decorator has already validated the app_id. The returned app_model is the app targeted. Therefore there is no need to pass app_id separately. Simply use app_model.id instead. So app_id is removed from the api routes. Related to: #11727 --- api/controllers/service_api/app/annotation.py | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py index 92921791ae4cb3..76dff8f25992bd 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -5,6 +5,7 @@ from controllers.service_api import api from controllers.service_api.wraps import FetchUserArg, WhereisUserArg, validate_app_token +from extensions.ext_redis import redis_client from fields.annotation_fields import ( annotation_fields, ) @@ -15,31 +16,46 @@ class AnnotationReplyActionApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) - def post(self, app_model: App, end_user: EndUser, app_id, action): - app_id = str(app_id) + def post(self, app_model: App, end_user: EndUser, action): parser = reqparse.RequestParser() parser.add_argument("score_threshold", required=True, type=float, location="json") parser.add_argument("embedding_provider_name", required=True, type=str, location="json") parser.add_argument("embedding_model_name", required=True, type=str, location="json") args = parser.parse_args() if action == "enable": - result = AppAnnotationService.enable_app_annotation(args, app_id) + result = AppAnnotationService.enable_app_annotation(args, app_model.id) elif action == "disable": - result = AppAnnotationService.disable_app_annotation(app_id) + result = AppAnnotationService.disable_app_annotation(app_model.id) else: raise ValueError("Unsupported annotation reply action") return result, 200 +class AnnotationReplyActionStatusApi(Resource): + @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) + def get(self, app_model: App, end_user: EndUser, job_id, action): + job_id = str(job_id) + app_annotation_job_key = "{}_app_annotation_job_{}".format(action, str(job_id)) + cache_result = redis_client.get(app_annotation_job_key) + if cache_result is None: + raise ValueError("The job is not exist.") + + job_status = cache_result.decode() + error_msg = "" + if job_status == "error": + app_annotation_error_key = "{}_app_annotation_error_{}".format(action, str(job_id)) + error_msg = redis_client.get(app_annotation_error_key).decode() + + return {"job_id": job_id, "job_status": job_status, "error_msg": error_msg}, 200 + class AnnotationListApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) - def get(self, app_model: App, end_user: EndUser, app_id): + def get(self, app_model: App, end_user: EndUser): page = request.args.get("page", default=1, type=int) limit = request.args.get("limit", default=20, type=int) keyword = request.args.get("keyword", default="", type=str) - app_id = str(app_id) - annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_id, page, limit, keyword) + annotation_list, total = AppAnnotationService.get_annotation_list_by_app_id(app_model.id, page, limit, keyword) response = { "data": marshal(annotation_list, annotation_fields), "has_more": len(annotation_list) == limit, @@ -51,44 +67,43 @@ def get(self, app_model: App, end_user: EndUser, app_id): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) @marshal_with(annotation_fields) - def post(self, app_model: App, end_user: EndUser, app_id): - print(current_user) - app_id = str(app_id) + def post(self, app_model: App, end_user: EndUser): parser = reqparse.RequestParser() parser.add_argument("question", required=True, type=str, location="json") parser.add_argument("answer", required=True, type=str, location="json") args = parser.parse_args() - annotation = AppAnnotationService.insert_app_annotation_directly(args, app_id) + annotation = AppAnnotationService.insert_app_annotation_directly(args, app_model.id) return annotation class AnnotationUpdateDeleteApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.JSON)) @marshal_with(annotation_fields) - def post(self, app_model: App, end_user: EndUser, app_id, annotation_id): + def post(self, app_model: App, end_user: EndUser, annotation_id): if not current_user.is_editor: raise Forbidden() - app_id = str(app_id) annotation_id = str(annotation_id) parser = reqparse.RequestParser() parser.add_argument("question", required=True, type=str, location="json") parser.add_argument("answer", required=True, type=str, location="json") args = parser.parse_args() - annotation = AppAnnotationService.update_app_annotation_directly(args, app_id, annotation_id) + annotation = AppAnnotationService.update_app_annotation_directly(args, app_model.id, annotation_id) return annotation @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) - def delete(self, app_model: App, end_user: EndUser, app_id, annotation_id): + def delete(self, app_model: App, end_user: EndUser, annotation_id): if not current_user.is_editor: raise Forbidden() - app_id = str(app_id) annotation_id = str(annotation_id) - AppAnnotationService.delete_app_annotation(app_id, annotation_id) + AppAnnotationService.delete_app_annotation(app_model.id, annotation_id) return {"result": "success"}, 200 -api.add_resource(AnnotationReplyActionApi, "/apps//annotation-reply/") -api.add_resource(AnnotationListApi, "/apps//annotations") -api.add_resource(AnnotationUpdateDeleteApi, "/apps//annotations/") \ No newline at end of file +api.add_resource(AnnotationReplyActionApi, "/apps/annotation-reply/") +api.add_resource( + AnnotationReplyActionStatusApi, "/apps/annotation-reply//status/" +) +api.add_resource(AnnotationListApi, "/apps/annotations") +api.add_resource(AnnotationUpdateDeleteApi, "/apps/annotations/") \ No newline at end of file From a7d85c26babbf4fbcc60e8d449c252e67b2997a3 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 2 Jan 2025 12:15:59 +0800 Subject: [PATCH 4/8] style: fixed ruff errors --- api/controllers/service_api/app/annotation.py | 4 ++-- api/controllers/service_api/workspace/models.py | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py index 76dff8f25992bd..472260f4b82149 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -1,4 +1,3 @@ - from flask import request from flask_restful import Resource, marshal, marshal_with, reqparse # type: ignore from werkzeug.exceptions import Forbidden @@ -30,6 +29,7 @@ def post(self, app_model: App, end_user: EndUser, action): raise ValueError("Unsupported annotation reply action") return result, 200 + class AnnotationReplyActionStatusApi(Resource): @validate_app_token(fetch_user_arg=FetchUserArg(fetch_from=WhereisUserArg.QUERY)) def get(self, app_model: App, end_user: EndUser, job_id, action): @@ -106,4 +106,4 @@ def delete(self, app_model: App, end_user: EndUser, annotation_id): AnnotationReplyActionStatusApi, "/apps/annotation-reply//status/" ) api.add_resource(AnnotationListApi, "/apps/annotations") -api.add_resource(AnnotationUpdateDeleteApi, "/apps/annotations/") \ No newline at end of file +api.add_resource(AnnotationUpdateDeleteApi, "/apps/annotations/") diff --git a/api/controllers/service_api/workspace/models.py b/api/controllers/service_api/workspace/models.py index 61439bc287a5cd..2a7715004137bc 100644 --- a/api/controllers/service_api/workspace/models.py +++ b/api/controllers/service_api/workspace/models.py @@ -1,14 +1,11 @@ -import logging - from flask_login import current_user # type: ignore -from flask_restful import Resource, reqparse # type: ignore -from werkzeug.exceptions import Forbidden - +from flask_restful import Resource # type: ignore from controllers.service_api import api from controllers.service_api.wraps import validate_dataset_token from core.model_runtime.utils.encoders import jsonable_encoder from services.model_provider_service import ModelProviderService + class ModelProviderAvailableModelApi(Resource): @validate_dataset_token def get(self, _, model_type): @@ -19,4 +16,5 @@ def get(self, _, model_type): return jsonable_encoder({"data": models}) + api.add_resource(ModelProviderAvailableModelApi, "/workspaces/current/models/model-types/") \ No newline at end of file From d589f0bbe84104a97d9d2f86c209f04b0e3510d4 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 2 Jan 2025 12:27:47 +0800 Subject: [PATCH 5/8] style: Fix ruff error --- api/controllers/service_api/workspace/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/controllers/service_api/workspace/models.py b/api/controllers/service_api/workspace/models.py index 2a7715004137bc..10d925bebd7b9b 100644 --- a/api/controllers/service_api/workspace/models.py +++ b/api/controllers/service_api/workspace/models.py @@ -1,5 +1,6 @@ from flask_login import current_user # type: ignore from flask_restful import Resource # type: ignore + from controllers.service_api import api from controllers.service_api.wraps import validate_dataset_token from core.model_runtime.utils.encoders import jsonable_encoder From b489355d8d6bc36e835cd1926bb27201c7ddb8d9 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 3 Jan 2025 11:04:03 +0800 Subject: [PATCH 6/8] style: Fix lint errors --- api/controllers/service_api/app/annotation.py | 4 +--- api/controllers/service_api/dataset/dataset.py | 4 ++-- api/controllers/service_api/workspace/models.py | 2 +- api/controllers/service_api/wraps.py | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/api/controllers/service_api/app/annotation.py b/api/controllers/service_api/app/annotation.py index 472260f4b82149..3d7fa786991214 100644 --- a/api/controllers/service_api/app/annotation.py +++ b/api/controllers/service_api/app/annotation.py @@ -102,8 +102,6 @@ def delete(self, app_model: App, end_user: EndUser, annotation_id): api.add_resource(AnnotationReplyActionApi, "/apps/annotation-reply/") -api.add_resource( - AnnotationReplyActionStatusApi, "/apps/annotation-reply//status/" -) +api.add_resource(AnnotationReplyActionStatusApi, "/apps/annotation-reply//status/") api.add_resource(AnnotationListApi, "/apps/annotations") api.add_resource(AnnotationUpdateDeleteApi, "/apps/annotations/") diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index 0b5e5b2130bf5f..c208cdf8ea4247 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -267,8 +267,8 @@ def patch(self, _, dataset_id): ) # clear partial member list when permission is only_me or all_team_members elif ( - data.get("permission") == DatasetPermissionEnum.ONLY_ME - or data.get("permission") == DatasetPermissionEnum.ALL_TEAM + data.get("permission") == DatasetPermissionEnum.ONLY_ME + or data.get("permission") == DatasetPermissionEnum.ALL_TEAM ): DatasetPermissionService.clear_partial_member_list(dataset_id_str) diff --git a/api/controllers/service_api/workspace/models.py b/api/controllers/service_api/workspace/models.py index 10d925bebd7b9b..373f8019f9ec2d 100644 --- a/api/controllers/service_api/workspace/models.py +++ b/api/controllers/service_api/workspace/models.py @@ -18,4 +18,4 @@ def get(self, _, model_type): return jsonable_encoder({"data": models}) -api.add_resource(ModelProviderAvailableModelApi, "/workspaces/current/models/model-types/") \ No newline at end of file +api.add_resource(ModelProviderAvailableModelApi, "/workspaces/current/models/model-types/") diff --git a/api/controllers/service_api/wraps.py b/api/controllers/service_api/wraps.py index 70891a58d64e1d..2cccbd2ceb91c1 100644 --- a/api/controllers/service_api/wraps.py +++ b/api/controllers/service_api/wraps.py @@ -214,7 +214,7 @@ def validate_and_get_api_token(scope=None): .filter( ApiToken.token == auth_token, ApiToken.type == scope, - ) + ) .first() ) @@ -241,7 +241,7 @@ def create_or_update_end_user_for_user_id(app_model: App, user_id: Optional[str] EndUser.app_id == app_model.id, EndUser.session_id == user_id, EndUser.type == "service_api", - ) + ) .first() ) From 8c8c87e11d916fd263aae0af3d3aad6fdd8aa6fd Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 16 Jan 2025 11:57:01 +0800 Subject: [PATCH 7/8] docs: Added docs in mdx files for the Dataset APIs implemented --- .../datasets/template/template.en.mdx | 293 ++++++++++++++++++ .../datasets/template/template.zh.mdx | 292 +++++++++++++++++ 2 files changed, 585 insertions(+) diff --git a/web/app/(commonLayout)/datasets/template/template.en.mdx b/web/app/(commonLayout)/datasets/template/template.en.mdx index d3dcfc4b24d598..c2f2f32ef0d100 100644 --- a/web/app/(commonLayout)/datasets/template/template.en.mdx +++ b/web/app/(commonLayout)/datasets/template/template.en.mdx @@ -371,6 +371,195 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
+ + + + ### Query + + + Knowledge Base ID + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", + "name": "Test Knowledge Base", + "description": "", + "provider": "vendor", + "permission": "only_me", + "data_source_type": null, + "indexing_technique": null, + "app_count": 0, + "document_count": 0, + "word_count": 0, + "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "created_at": 1735620612, + "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "updated_at": 1735620612, + "embedding_model": null, + "embedding_model_provider": null, + "embedding_available": true, + "retrieval_model_dict": { + "search_method": "semantic_search", + "reranking_enable": false, + "reranking_mode": null, + "reranking_model": { + "reranking_provider_name": "", + "reranking_model_name": "" + }, + "weights": null, + "top_k": 2, + "score_threshold_enabled": false, + "score_threshold": null + }, + "tags": [], + "doc_form": null, + "external_knowledge_info": { + "external_knowledge_id": null, + "external_knowledge_api_id": null, + "external_knowledge_api_name": null, + "external_knowledge_api_endpoint": null + }, + "external_retrieval_model": { + "top_k": 2, + "score_threshold": 0.0, + "score_threshold_enabled": null + } + } + ``` + + + + +
+ + + + + ### Query + + + Knowledge Base ID + + + Index technique (optional) + - high_quality High quality + - economy Economy + + + Permission + - only_me Only me + - all_team_members All team members + - partial_members Partial members + + + Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional) + + + Specified embedding model, corresponding to the model field(Optional) + + + Specified retrieval model, corresponding to the model field(Optional) + + + Partial member list(Optional) + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "Test Knowledge Base", "indexing_technique": "high_quality", "permission": "only_me",\ + "embedding_model_provider": "zhipuai", "embedding_model": "embedding-3", "retrieval_model": "", "partial_member_list": []}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", + "name": "Test Knowledge Base", + "description": "", + "provider": "vendor", + "permission": "only_me", + "data_source_type": null, + "indexing_technique": "high_quality", + "app_count": 0, + "document_count": 0, + "word_count": 0, + "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "created_at": 1735620612, + "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "updated_at": 1735622679, + "embedding_model": "embedding-3", + "embedding_model_provider": "zhipuai", + "embedding_available": null, + "retrieval_model_dict": { + "search_method": "semantic_search", + "reranking_enable": false, + "reranking_mode": null, + "reranking_model": { + "reranking_provider_name": "", + "reranking_model_name": "" + }, + "weights": null, + "top_k": 2, + "score_threshold_enabled": false, + "score_threshold": null + }, + "tags": [], + "doc_form": null, + "external_knowledge_info": { + "external_knowledge_id": null, + "external_knowledge_api_id": null, + "external_knowledge_api_name": null, + "external_knowledge_api_endpoint": null + }, + "external_retrieval_model": { + "top_k": 2, + "score_threshold": 0.0, + "score_threshold_enabled": null + }, + "partial_member_list": [] + } + ``` + + + + +
+ + + + + ### Query + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/workspaces/current/models/model-types/text-embedding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + + + ```json {{ title: 'Response' }} + { + "data": [ + { + "provider": "zhipuai", + "label": { + "zh_Hans": "智谱 AI", + "en_US": "ZHIPU AI" + }, + "icon_small": { + "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/zh_Hans", + "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/en_US" + }, + "icon_large": { + "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/zh_Hans", + "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/en_US" + }, + "status": "active", + "models": [ + { + "model": "embedding-3", + "label": { + "zh_Hans": "embedding-3", + "en_US": "embedding-3" + }, + "model_type": "text-embedding", + "features": null, + "fetch_from": "predefined-model", + "model_properties": { + "context_size": 8192 + }, + "deprecated": false, + "status": "active", + "load_balancing_enabled": false + }, + { + "model": "embedding-2", + "label": { + "zh_Hans": "embedding-2", + "en_US": "embedding-2" + }, + "model_type": "text-embedding", + "features": null, + "fetch_from": "predefined-model", + "model_properties": { + "context_size": 8192 + }, + "deprecated": false, + "status": "active", + "load_balancing_enabled": false + }, + { + "model": "text_embedding", + "label": { + "zh_Hans": "text_embedding", + "en_US": "text_embedding" + }, + "model_type": "text-embedding", + "features": null, + "fetch_from": "predefined-model", + "model_properties": { + "context_size": 512 + }, + "deprecated": false, + "status": "active", + "load_balancing_enabled": false + } + ] + } + ] + } + ``` + + + + +
+ ### Error message diff --git a/web/app/(commonLayout)/datasets/template/template.zh.mdx b/web/app/(commonLayout)/datasets/template/template.zh.mdx index db15ede9fcabf2..d799d54fa20c92 100644 --- a/web/app/(commonLayout)/datasets/template/template.zh.mdx +++ b/web/app/(commonLayout)/datasets/template/template.zh.mdx @@ -371,6 +371,195 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
+ + + + ### Query + + + 知识库 ID + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/datasets/{dataset_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", + "name": "Test Knowledge Base", + "description": "", + "provider": "vendor", + "permission": "only_me", + "data_source_type": null, + "indexing_technique": null, + "app_count": 0, + "document_count": 0, + "word_count": 0, + "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "created_at": 1735620612, + "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "updated_at": 1735620612, + "embedding_model": null, + "embedding_model_provider": null, + "embedding_available": true, + "retrieval_model_dict": { + "search_method": "semantic_search", + "reranking_enable": false, + "reranking_mode": null, + "reranking_model": { + "reranking_provider_name": "", + "reranking_model_name": "" + }, + "weights": null, + "top_k": 2, + "score_threshold_enabled": false, + "score_threshold": null + }, + "tags": [], + "doc_form": null, + "external_knowledge_info": { + "external_knowledge_id": null, + "external_knowledge_api_id": null, + "external_knowledge_api_name": null, + "external_knowledge_api_endpoint": null + }, + "external_retrieval_model": { + "top_k": 2, + "score_threshold": 0.0, + "score_threshold_enabled": null + } + } + ``` + + + + +
+ + + + + ### Query + + + 知识库 ID + + + 索引模式(选填,建议填写) + - high_quality 高质量 + - economy 经济 + + + 权限(选填,默认 only_me) + - only_me 仅自己 + - all_team_members 所有团队成员 + - partial_members 部分团队成员 + + + 嵌入模型提供商(选填), 必须先在系统内设定好接入的模型,对应的是provider字段 + + + 嵌入模型(选填) + + + 检索模型(选填) + + + 部分团队成员 ID 列表(选填) + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/datasets/{dataset_id}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{"name": "Test Knowledge Base", "indexing_technique": "high_quality", "permission": "only_me",\ + "embedding_model_provider": "zhipuai", "embedding_model": "embedding-3", "retrieval_model": "", "partial_member_list": []}' + ``` + + + ```json {{ title: 'Response' }} + { + "id": "eaedb485-95ac-4ffd-ab1e-18da6d676a2f", + "name": "Test Knowledge Base", + "description": "", + "provider": "vendor", + "permission": "only_me", + "data_source_type": null, + "indexing_technique": "high_quality", + "app_count": 0, + "document_count": 0, + "word_count": 0, + "created_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "created_at": 1735620612, + "updated_by": "e99a1635-f725-4951-a99a-1daaaa76cfc6", + "updated_at": 1735622679, + "embedding_model": "embedding-3", + "embedding_model_provider": "zhipuai", + "embedding_available": null, + "retrieval_model_dict": { + "search_method": "semantic_search", + "reranking_enable": false, + "reranking_mode": null, + "reranking_model": { + "reranking_provider_name": "", + "reranking_model_name": "" + }, + "weights": null, + "top_k": 2, + "score_threshold_enabled": false, + "score_threshold": null + }, + "tags": [], + "doc_form": null, + "external_knowledge_info": { + "external_knowledge_id": null, + "external_knowledge_api_id": null, + "external_knowledge_api_name": null, + "external_knowledge_api_endpoint": null + }, + "external_retrieval_model": { + "top_k": 2, + "score_threshold": 0.0, + "score_threshold_enabled": null + }, + "partial_member_list": [] + } + ``` + + + + +
+
+
+ + + + + ### Query + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/workspaces/current/models/model-types/text-embedding' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + ``` + + + ```json {{ title: 'Response' }} + { + "data": [ + { + "provider": "zhipuai", + "label": { + "zh_Hans": "智谱 AI", + "en_US": "ZHIPU AI" + }, + "icon_small": { + "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/zh_Hans", + "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_small/en_US" + }, + "icon_large": { + "zh_Hans": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/zh_Hans", + "en_US": "http://127.0.0.1:5001/console/api/workspaces/current/model-providers/zhipuai/icon_large/en_US" + }, + "status": "active", + "models": [ + { + "model": "embedding-3", + "label": { + "zh_Hans": "embedding-3", + "en_US": "embedding-3" + }, + "model_type": "text-embedding", + "features": null, + "fetch_from": "predefined-model", + "model_properties": { + "context_size": 8192 + }, + "deprecated": false, + "status": "active", + "load_balancing_enabled": false + }, + { + "model": "embedding-2", + "label": { + "zh_Hans": "embedding-2", + "en_US": "embedding-2" + }, + "model_type": "text-embedding", + "features": null, + "fetch_from": "predefined-model", + "model_properties": { + "context_size": 8192 + }, + "deprecated": false, + "status": "active", + "load_balancing_enabled": false + }, + { + "model": "text_embedding", + "label": { + "zh_Hans": "text_embedding", + "en_US": "text_embedding" + }, + "model_type": "text-embedding", + "features": null, + "fetch_from": "predefined-model", + "model_properties": { + "context_size": 512 + }, + "deprecated": false, + "status": "active", + "load_balancing_enabled": false + } + ] + } + ] + } + ``` + + +
From 6a8c6035f95987209f791e0d0bde660bb7931c1d Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 16 Jan 2025 12:34:20 +0800 Subject: [PATCH 8/8] docs: Added annotation API documentation to Chat App documentation. It is added to Chat App due to Annotation is under Apps, and there is no individual Annotation API doc page. It seems correct to have Annotation APIs put here. --- .../develop/template/template.zh.mdx | 301 +++++++++++++++++ .../template/template_advanced_chat.en.mdx | 301 +++++++++++++++++ .../template/template_advanced_chat.zh.mdx | 303 ++++++++++++++++++ .../develop/template/template_chat.en.mdx | 301 +++++++++++++++++ 4 files changed, 1206 insertions(+) diff --git a/web/app/components/develop/template/template.zh.mdx b/web/app/components/develop/template/template.zh.mdx index d8cabb536d4dc0..b8c20d6985a089 100755 --- a/web/app/components/develop/template/template.zh.mdx +++ b/web/app/components/develop/template/template.zh.mdx @@ -548,3 +548,304 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' +--- + + + + + ### Query + + + 页码 + + + 每页数量 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotations?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + ], + "has_more": false, + "limit": 20, + "total": 1, + "page": 1 + } + ``` + + + +--- + + + + + ### Query + + + 问题 + + + 答案内容 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + 标注 ID + + + 问题 + + + 答案内容 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + 标注 ID + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + +--- + + + + + ### Query + + + 动作,只能是 'enable' 或 'disable' + + + 指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段 + + + 指定的嵌入模型,对应的是model字段 + + + 相似度阈值,当相似度大于该阈值时,系统会自动回复,否则不回复 + + + + + 嵌入模型的提供商和模型名称可以通过以下接口获取:v1/workspaces/current/models/model-types/text-embedding, 具体见:通过 API 维护知识库。 使用的Authorization是Dataset的API Token。 + + ```bash {{ title: 'cURL' }} + curl --location --request POST 'https://api.dify.ai/v1/apps/annotation-reply/{action}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "score_threshold": 0.9, + "embedding_provider_name": "zhipu", + "embedding_model_name": "embedding_3" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting" + } + ``` + 该接口是异步执行,所以会返回一个job_id,通过查询job状态接口可以获取到最终的执行结果。 + + + +--- + + + + + ### Query + + + 动作,只能是 'enable' 或 'disable',并且必须和标注回复初始设置接口的动作一致 + + + 任务 ID,从标注回复初始设置接口返回的 job_id + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotation-reply/{action}/status/{job_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting", + "error_msg": "" + } + ``` + + + diff --git a/web/app/components/develop/template/template_advanced_chat.en.mdx b/web/app/components/develop/template/template_advanced_chat.en.mdx index e905e9e0c62aa7..94dbb66ed67585 100644 --- a/web/app/components/develop/template/template_advanced_chat.en.mdx +++ b/web/app/components/develop/template/template_advanced_chat.en.mdx @@ -1137,3 +1137,304 @@ Chat applications support session persistence, allowing previous chat history to +--- + + + + + ### Query + + + Page number + + + Number of items returned, default 20, range 1-100 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotations?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + ], + "has_more": false, + "limit": 20, + "total": 1, + "page": 1 + } + ``` + + + +--- + + + + + ### Query + + + Question + + + Answer + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + Annotation ID + + + Question + + + Answer + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + Annotation ID + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + +--- + + + + + ### Query + + + Action, can only be 'enable' or 'disable' + + + Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional) + + + Specified embedding model, corresponding to the model field(Optional) + + + The similarity threshold for matching annotated replies. Only annotations with scores above this threshold will be recalled. + + + + + The provider and model name of the embedding model can be obtained through the following interface: v1/workspaces/current/models/model-types/text-embedding. For specific instructions, see: Maintain Knowledge Base via API. The Authorization used is the Dataset API Token. + + ```bash {{ title: 'cURL' }} + curl --location --request POST 'https://api.dify.ai/v1/apps/annotation-reply/{action}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "score_threshold": 0.9, + "embedding_provider_name": "zhipu", + "embedding_model_name": "embedding_3" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting" + } + ``` + This interface is executed asynchronously, so it will return a job_id. You can get the final execution result by querying the job status interface. + + + +--- + + + + + ### Query + + + Action, can only be 'enable' or 'disable', must be the same as the action in the initial annotation reply settings interface + + + Job ID, obtained from the initial annotation reply settings interface + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotation-reply/{action}/status/{job_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting", + "error_msg": "" + } + ``` + + + diff --git a/web/app/components/develop/template/template_advanced_chat.zh.mdx b/web/app/components/develop/template/template_advanced_chat.zh.mdx index 8d181d7e0b7581..25a0d02253a0fe 100755 --- a/web/app/components/develop/template/template_advanced_chat.zh.mdx +++ b/web/app/components/develop/template/template_advanced_chat.zh.mdx @@ -1159,3 +1159,306 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' +--- + +--- + + + + + ### Query + + + 页码 + + + 每页数量 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotations?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + ], + "has_more": false, + "limit": 20, + "total": 1, + "page": 1 + } + ``` + + + +--- + + + + + ### Query + + + 问题 + + + 答案内容 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + 标注 ID + + + 问题 + + + 答案内容 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + 标注 ID + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + +--- + + + + + ### Query + + + 动作,只能是 'enable' 或 'disable' + + + 指定的嵌入模型提供商, 必须先在系统内设定好接入的模型,对应的是provider字段 + + + 指定的嵌入模型,对应的是model字段 + + + 相似度阈值,当相似度大于该阈值时,系统会自动回复,否则不回复 + + + + + 嵌入模型的提供商和模型名称可以通过以下接口获取:v1/workspaces/current/models/model-types/text-embedding, 具体见:通过 API 维护知识库。 使用的Authorization是Dataset的API Token。 + + ```bash {{ title: 'cURL' }} + curl --location --request POST 'https://api.dify.ai/v1/apps/annotation-reply/{action}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "score_threshold": 0.9, + "embedding_provider_name": "zhipu", + "embedding_model_name": "embedding_3" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting" + } + ``` + 该接口是异步执行,所以会返回一个job_id,通过查询job状态接口可以获取到最终的执行结果。 + + + +--- + + + + + ### Query + + + 动作,只能是 'enable' 或 'disable',并且必须和标注回复初始设置接口的动作一致 + + + 任务 ID,从标注回复初始设置接口返回的 job_id + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotation-reply/{action}/status/{job_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting", + "error_msg": "" + } + ``` + + + diff --git a/web/app/components/develop/template/template_chat.en.mdx b/web/app/components/develop/template/template_chat.en.mdx index 7de329a7a0ed0e..35fee3a9fdbcc3 100644 --- a/web/app/components/develop/template/template_chat.en.mdx +++ b/web/app/components/develop/template/template_chat.en.mdx @@ -1173,3 +1173,304 @@ Chat applications support session persistence, allowing previous chat history to +--- + + + + + ### Query + + + Page number + + + Number of items returned, default 20, range 1-100 + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotations?page=1&limit=20' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "data": [ + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + ], + "has_more": false, + "limit": 20, + "total": 1, + "page": 1 + } + ``` + + + +--- + + + + + ### Query + + + Question + + + Answer + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + Annotation ID + + + Question + + + Answer + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request POST '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "question": "What is your name?", + "answer": "I am Dify." + }' + ``` + + + + ```json {{ title: 'Response' }} + { + { + "id": "69d48372-ad81-4c75-9c46-2ce197b4d402", + "question": "What is your name?", + "answer": "I am Dify.", + "hit_count": 0, + "created_at": 1735625869 + } + } + ``` + + + +--- + + + + + ### Query + + + Annotation ID + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request DELETE '${props.apiBaseUrl}/apps/annotations/{annotation_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + {"result": "success"} + ``` + + + +--- + + + + + ### Query + + + Action, can only be 'enable' or 'disable' + + + Specified embedding model provider, must be set up in the system first, corresponding to the provider field(Optional) + + + Specified embedding model, corresponding to the model field(Optional) + + + The similarity threshold for matching annotated replies. Only annotations with scores above this threshold will be recalled. + + + + + The provider and model name of the embedding model can be obtained through the following interface: v1/workspaces/current/models/model-types/text-embedding. For specific instructions, see: Maintain Knowledge Base via API. The Authorization used is the Dataset API Token. + + ```bash {{ title: 'cURL' }} + curl --location --request POST 'https://api.dify.ai/v1/apps/annotation-reply/{action}' \ + --header 'Authorization: Bearer {api_key}' \ + --header 'Content-Type: application/json' \ + --data-raw '{ + "score_threshold": 0.9, + "embedding_provider_name": "zhipu", + "embedding_model_name": "embedding_3" + }' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting" + } + ``` + This interface is executed asynchronously, so it will return a job_id. You can get the final execution result by querying the job status interface. + + + +--- + + + + + ### Query + + + Action, can only be 'enable' or 'disable', must be the same as the action in the initial annotation reply settings interface + + + Job ID, + + + + + + ```bash {{ title: 'cURL' }} + curl --location --request GET '${props.apiBaseUrl}/apps/annotation-reply/{action}/status/{job_id}' \ + --header 'Authorization: Bearer {api_key}' + ``` + + + + ```json {{ title: 'Response' }} + { + "job_id": "b15c8f68-1cf4-4877-bf21-ed7cf2011802", + "job_status": "waiting", + "error_msg": "" + } + ``` + + +