diff --git a/api/apps/sdk/assistant.py b/api/apps/sdk/assistant.py deleted file mode 100644 index fe059ccc03..0000000000 --- a/api/apps/sdk/assistant.py +++ /dev/null @@ -1,304 +0,0 @@ -# -# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -from flask import request - -from api.db import StatusEnum -from api.db.db_models import TenantLLM -from api.db.services.dialog_service import DialogService -from api.db.services.knowledgebase_service import KnowledgebaseService -from api.db.services.llm_service import LLMService, TenantLLMService -from api.db.services.user_service import TenantService -from api.settings import RetCode -from api.utils import get_uuid -from api.utils.api_utils import get_data_error_result, token_required -from api.utils.api_utils import get_json_result - - -@manager.route('/save', methods=['POST']) -@token_required -def save(tenant_id): - req = request.json - # dataset - if req.get("knowledgebases") == []: - return get_data_error_result(retmsg="knowledgebases can not be empty list") - kb_list = [] - if req.get("knowledgebases"): - for kb in req.get("knowledgebases"): - if not kb["id"]: - return get_data_error_result(retmsg="knowledgebase needs id") - if not KnowledgebaseService.query(id=kb["id"], tenant_id=tenant_id): - return get_data_error_result(retmsg="you do not own the knowledgebase") - # if not DocumentService.query(kb_id=kb["id"]): - # return get_data_error_result(retmsg="There is a invalid knowledgebase") - kb_list.append(kb["id"]) - req["kb_ids"] = kb_list - # llm - llm = req.get("llm") - if llm: - if "model_name" in llm: - req["llm_id"] = llm.pop("model_name") - req["llm_setting"] = req.pop("llm") - e, tenant = TenantService.get_by_id(tenant_id) - if not e: - return get_data_error_result(retmsg="Tenant not found!") - # prompt - prompt = req.get("prompt") - key_mapping = {"parameters": "variables", - "prologue": "opener", - "quote": "show_quote", - "system": "prompt", - "rerank_id": "rerank_model", - "vector_similarity_weight": "keywords_similarity_weight"} - key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"] - if prompt: - for new_key, old_key in key_mapping.items(): - if old_key in prompt: - prompt[new_key] = prompt.pop(old_key) - for key in key_list: - if key in prompt: - req[key] = prompt.pop(key) - req["prompt_config"] = req.pop("prompt") - # create - if "id" not in req: - # dataset - if not kb_list: - return get_data_error_result(retmsg="knowledgebases are required!") - # init - req["id"] = get_uuid() - req["description"] = req.get("description", "A helpful Assistant") - req["icon"] = req.get("avatar", "") - req["top_n"] = req.get("top_n", 6) - req["top_k"] = req.get("top_k", 1024) - req["rerank_id"] = req.get("rerank_id", "") - if req.get("llm_id"): - if not TenantLLMService.query(llm_name=req["llm_id"]): - return get_data_error_result(retmsg="the model_name does not exist.") - else: - req["llm_id"] = tenant.llm_id - if not req.get("name"): - return get_data_error_result(retmsg="name is required.") - if DialogService.query(name=req["name"], tenant_id=tenant_id, status=StatusEnum.VALID.value): - return get_data_error_result(retmsg="Duplicated assistant name in creating dataset.") - # tenant_id - if req.get("tenant_id"): - return get_data_error_result(retmsg="tenant_id must not be provided.") - req["tenant_id"] = tenant_id - # prompt more parameter - default_prompt = { - "system": """你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。 - 以下是知识库: - {knowledge} - 以上是知识库。""", - "prologue": "您好,我是您的助手小樱,长得可爱又善良,can I help you?", - "parameters": [ - {"key": "knowledge", "optional": False} - ], - "empty_response": "Sorry! 知识库中未找到相关内容!" - } - key_list_2 = ["system", "prologue", "parameters", "empty_response"] - if "prompt_config" not in req: - req['prompt_config'] = {} - for key in key_list_2: - temp = req['prompt_config'].get(key) - if not temp: - req['prompt_config'][key] = default_prompt[key] - for p in req['prompt_config']["parameters"]: - if p["optional"]: - continue - if req['prompt_config']["system"].find("{%s}" % p["key"]) < 0: - return get_data_error_result( - retmsg="Parameter '{}' is not used".format(p["key"])) - # save - if not DialogService.save(**req): - return get_data_error_result(retmsg="Fail to new an assistant!") - # response - e, res = DialogService.get_by_id(req["id"]) - if not e: - return get_data_error_result(retmsg="Fail to new an assistant!") - res = res.to_json() - renamed_dict = {} - for key, value in res["prompt_config"].items(): - new_key = key_mapping.get(key, key) - renamed_dict[new_key] = value - res["prompt"] = renamed_dict - del res["prompt_config"] - new_dict = {"similarity_threshold": res["similarity_threshold"], - "keywords_similarity_weight": res["vector_similarity_weight"], - "top_n": res["top_n"], - "rerank_model": res['rerank_id']} - res["prompt"].update(new_dict) - for key in key_list: - del res[key] - res["llm"] = res.pop("llm_setting") - res["llm"]["model_name"] = res.pop("llm_id") - del res["kb_ids"] - res["knowledgebases"] = req["knowledgebases"] - res["avatar"] = res.pop("icon") - return get_json_result(data=res) - else: - # authorization - if not DialogService.query(tenant_id=tenant_id, id=req["id"], status=StatusEnum.VALID.value): - return get_json_result(data=False, retmsg='You do not own the assistant', retcode=RetCode.OPERATING_ERROR) - # prompt - if not req["id"]: - return get_data_error_result(retmsg="id can not be empty") - e, res = DialogService.get_by_id(req["id"]) - res = res.to_json() - if "llm_id" in req: - if not TenantLLMService.query(llm_name=req["llm_id"]): - return get_data_error_result(retmsg="the model_name does not exist.") - if "name" in req: - if not req.get("name"): - return get_data_error_result(retmsg="name is not empty.") - if req["name"].lower() != res["name"].lower() \ - and len( - DialogService.query(name=req["name"], tenant_id=tenant_id, status=StatusEnum.VALID.value)) > 0: - return get_data_error_result(retmsg="Duplicated assistant name in updating dataset.") - if "prompt_config" in req: - res["prompt_config"].update(req["prompt_config"]) - for p in res["prompt_config"]["parameters"]: - if p["optional"]: - continue - if res["prompt_config"]["system"].find("{%s}" % p["key"]) < 0: - return get_data_error_result(retmsg="Parameter '{}' is not used".format(p["key"])) - if "llm_setting" in req: - res["llm_setting"].update(req["llm_setting"]) - req["prompt_config"] = res["prompt_config"] - req["llm_setting"] = res["llm_setting"] - # avatar - if "avatar" in req: - req["icon"] = req.pop("avatar") - assistant_id = req.pop("id") - if "knowledgebases" in req: - req.pop("knowledgebases") - if not DialogService.update_by_id(assistant_id, req): - return get_data_error_result(retmsg="Assistant not found!") - return get_json_result(data=True) - - -@manager.route('/delete', methods=['DELETE']) -@token_required -def delete(tenant_id): - req = request.args - if "id" not in req: - return get_data_error_result(retmsg="id is required") - id = req['id'] - if not DialogService.query(tenant_id=tenant_id, id=id, status=StatusEnum.VALID.value): - return get_json_result(data=False, retmsg='you do not own the assistant.', retcode=RetCode.OPERATING_ERROR) - - temp_dict = {"status": StatusEnum.INVALID.value} - DialogService.update_by_id(req["id"], temp_dict) - return get_json_result(data=True) - - -@manager.route('/get', methods=['GET']) -@token_required -def get(tenant_id): - req = request.args - if "id" in req: - id = req["id"] - ass = DialogService.query(tenant_id=tenant_id, id=id, status=StatusEnum.VALID.value) - if not ass: - return get_json_result(data=False, retmsg='You do not own the assistant.', retcode=RetCode.OPERATING_ERROR) - if "name" in req: - name = req["name"] - if ass[0].name != name: - return get_json_result(data=False, retmsg='name does not match id.', retcode=RetCode.OPERATING_ERROR) - res = ass[0].to_json() - else: - if "name" in req: - name = req["name"] - ass = DialogService.query(name=name, tenant_id=tenant_id, status=StatusEnum.VALID.value) - if not ass: - return get_json_result(data=False, retmsg='You do not own the assistant.', - retcode=RetCode.OPERATING_ERROR) - res = ass[0].to_json() - else: - return get_data_error_result(retmsg="At least one of `id` or `name` must be provided.") - renamed_dict = {} - key_mapping = {"parameters": "variables", - "prologue": "opener", - "quote": "show_quote", - "system": "prompt", - "rerank_id": "rerank_model", - "vector_similarity_weight": "keywords_similarity_weight"} - key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"] - for key, value in res["prompt_config"].items(): - new_key = key_mapping.get(key, key) - renamed_dict[new_key] = value - res["prompt"] = renamed_dict - del res["prompt_config"] - new_dict = {"similarity_threshold": res["similarity_threshold"], - "keywords_similarity_weight": res["vector_similarity_weight"], - "top_n": res["top_n"], - "rerank_model": res['rerank_id']} - res["prompt"].update(new_dict) - for key in key_list: - del res[key] - res["llm"] = res.pop("llm_setting") - res["llm"]["model_name"] = res.pop("llm_id") - kb_list = [] - for kb_id in res["kb_ids"]: - kb = KnowledgebaseService.query(id=kb_id) - kb_list.append(kb[0].to_json()) - del res["kb_ids"] - res["knowledgebases"] = kb_list - res["avatar"] = res.pop("icon") - return get_json_result(data=res) - - -@manager.route('/list', methods=['GET']) -@token_required -def list_assistants(tenant_id): - assts = DialogService.query( - tenant_id=tenant_id, - status=StatusEnum.VALID.value, - reverse=True, - order_by=DialogService.model.create_time) - assts = [d.to_dict() for d in assts] - list_assts = [] - renamed_dict = {} - key_mapping = {"parameters": "variables", - "prologue": "opener", - "quote": "show_quote", - "system": "prompt", - "rerank_id": "rerank_model", - "vector_similarity_weight": "keywords_similarity_weight"} - key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"] - for res in assts: - for key, value in res["prompt_config"].items(): - new_key = key_mapping.get(key, key) - renamed_dict[new_key] = value - res["prompt"] = renamed_dict - del res["prompt_config"] - new_dict = {"similarity_threshold": res["similarity_threshold"], - "keywords_similarity_weight": res["vector_similarity_weight"], - "top_n": res["top_n"], - "rerank_model": res['rerank_id']} - res["prompt"].update(new_dict) - for key in key_list: - del res[key] - res["llm"] = res.pop("llm_setting") - res["llm"]["model_name"] = res.pop("llm_id") - kb_list = [] - for kb_id in res["kb_ids"]: - kb = KnowledgebaseService.query(id=kb_id) - kb_list.append(kb[0].to_json()) - del res["kb_ids"] - res["knowledgebases"] = kb_list - res["avatar"] = res.pop("icon") - list_assts.append(res) - return get_json_result(data=list_assts) diff --git a/api/apps/sdk/chat.py b/api/apps/sdk/chat.py new file mode 100644 index 0000000000..0450b48492 --- /dev/null +++ b/api/apps/sdk/chat.py @@ -0,0 +1,287 @@ +# +# Copyright 2024 The InfiniFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from flask import request + +from api.db import StatusEnum +from api.db.db_models import TenantLLM +from api.db.services.dialog_service import DialogService +from api.db.services.knowledgebase_service import KnowledgebaseService +from api.db.services.llm_service import LLMService, TenantLLMService +from api.db.services.user_service import TenantService +from api.settings import RetCode +from api.utils import get_uuid +from api.utils.api_utils import get_error_data_result, token_required +from api.utils.api_utils import get_result + +@manager.route('/chat', methods=['POST']) +@token_required +def create(tenant_id): + req=request.json + if not req.get("knowledgebases"): + return get_error_data_result(retmsg="knowledgebases are required") + kb_list = [] + for kb in req.get("knowledgebases"): + if not kb["id"]: + return get_error_data_result(retmsg="knowledgebase needs id") + if not KnowledgebaseService.query(id=kb["id"], tenant_id=tenant_id): + return get_error_data_result(retmsg="you do not own the knowledgebase") + # if not DocumentService.query(kb_id=kb["id"]): + # return get_error_data_result(retmsg="There is a invalid knowledgebase") + kb_list.append(kb["id"]) + req["kb_ids"] = kb_list + # llm + llm = req.get("llm") + if llm: + if "model_name" in llm: + req["llm_id"] = llm.pop("model_name") + req["llm_setting"] = req.pop("llm") + e, tenant = TenantService.get_by_id(tenant_id) + if not e: + return get_error_data_result(retmsg="Tenant not found!") + # prompt + prompt = req.get("prompt") + key_mapping = {"parameters": "variables", + "prologue": "opener", + "quote": "show_quote", + "system": "prompt", + "rerank_id": "rerank_model", + "vector_similarity_weight": "keywords_similarity_weight"} + key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"] + if prompt: + for new_key, old_key in key_mapping.items(): + if old_key in prompt: + prompt[new_key] = prompt.pop(old_key) + for key in key_list: + if key in prompt: + req[key] = prompt.pop(key) + req["prompt_config"] = req.pop("prompt") + # init + req["id"] = get_uuid() + req["description"] = req.get("description", "A helpful Assistant") + req["icon"] = req.get("avatar", "") + req["top_n"] = req.get("top_n", 6) + req["top_k"] = req.get("top_k", 1024) + req["rerank_id"] = req.get("rerank_id", "") + if req.get("llm_id"): + if not TenantLLMService.query(llm_name=req["llm_id"]): + return get_error_data_result(retmsg="the model_name does not exist.") + else: + req["llm_id"] = tenant.llm_id + if not req.get("name"): + return get_error_data_result(retmsg="name is required.") + if DialogService.query(name=req["name"], tenant_id=tenant_id, status=StatusEnum.VALID.value): + return get_error_data_result(retmsg="Duplicated chat name in creating dataset.") + # tenant_id + if req.get("tenant_id"): + return get_error_data_result(retmsg="tenant_id must not be provided.") + req["tenant_id"] = tenant_id + # prompt more parameter + default_prompt = { + "system": """你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。 + 以下是知识库: + {knowledge} + 以上是知识库。""", + "prologue": "您好,我是您的助手小樱,长得可爱又善良,can I help you?", + "parameters": [ + {"key": "knowledge", "optional": False} + ], + "empty_response": "Sorry! 知识库中未找到相关内容!" + } + key_list_2 = ["system", "prologue", "parameters", "empty_response"] + if "prompt_config" not in req: + req['prompt_config'] = {} + for key in key_list_2: + temp = req['prompt_config'].get(key) + if not temp: + req['prompt_config'][key] = default_prompt[key] + for p in req['prompt_config']["parameters"]: + if p["optional"]: + continue + if req['prompt_config']["system"].find("{%s}" % p["key"]) < 0: + return get_error_data_result( + retmsg="Parameter '{}' is not used".format(p["key"])) + # save + if not DialogService.save(**req): + return get_error_data_result(retmsg="Fail to new a chat!") + # response + e, res = DialogService.get_by_id(req["id"]) + if not e: + return get_error_data_result(retmsg="Fail to new a chat!") + res = res.to_json() + renamed_dict = {} + for key, value in res["prompt_config"].items(): + new_key = key_mapping.get(key, key) + renamed_dict[new_key] = value + res["prompt"] = renamed_dict + del res["prompt_config"] + new_dict = {"similarity_threshold": res["similarity_threshold"], + "keywords_similarity_weight": res["vector_similarity_weight"], + "top_n": res["top_n"], + "rerank_model": res['rerank_id']} + res["prompt"].update(new_dict) + for key in key_list: + del res[key] + res["llm"] = res.pop("llm_setting") + res["llm"]["model_name"] = res.pop("llm_id") + del res["kb_ids"] + res["knowledgebases"] = req["knowledgebases"] + res["avatar"] = res.pop("icon") + return get_result(data=res) + +@manager.route('/chat/', methods=['PUT']) +@token_required +def update(tenant_id,chat_id): + if not DialogService.query(tenant_id=tenant_id, id=chat_id, status=StatusEnum.VALID.value): + return get_error_data_result(retmsg='You do not own the chat') + req =request.json + if "knowledgebases" in req: + if not req.get("knowledgebases"): + return get_error_data_result(retmsg="knowledgebases can't be empty value") + kb_list = [] + for kb in req.get("knowledgebases"): + if not kb["id"]: + return get_error_data_result(retmsg="knowledgebase needs id") + if not KnowledgebaseService.query(id=kb["id"], tenant_id=tenant_id): + return get_error_data_result(retmsg="you do not own the knowledgebase") + # if not DocumentService.query(kb_id=kb["id"]): + # return get_error_data_result(retmsg="There is a invalid knowledgebase") + kb_list.append(kb["id"]) + req["kb_ids"] = kb_list + llm = req.get("llm") + if llm: + if "model_name" in llm: + req["llm_id"] = llm.pop("model_name") + req["llm_setting"] = req.pop("llm") + e, tenant = TenantService.get_by_id(tenant_id) + if not e: + return get_error_data_result(retmsg="Tenant not found!") + # prompt + prompt = req.get("prompt") + key_mapping = {"parameters": "variables", + "prologue": "opener", + "quote": "show_quote", + "system": "prompt", + "rerank_id": "rerank_model", + "vector_similarity_weight": "keywords_similarity_weight"} + key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"] + if prompt: + for new_key, old_key in key_mapping.items(): + if old_key in prompt: + prompt[new_key] = prompt.pop(old_key) + for key in key_list: + if key in prompt: + req[key] = prompt.pop(key) + req["prompt_config"] = req.pop("prompt") + e, res = DialogService.get_by_id(chat_id) + res = res.to_json() + if "llm_id" in req: + if not TenantLLMService.query(llm_name=req["llm_id"]): + return get_error_data_result(retmsg="the model_name does not exist.") + if "name" in req: + if not req.get("name"): + return get_error_data_result(retmsg="name is not empty.") + if req["name"].lower() != res["name"].lower() \ + and len( + DialogService.query(name=req["name"], tenant_id=tenant_id, status=StatusEnum.VALID.value)) > 0: + return get_error_data_result(retmsg="Duplicated chat name in updating dataset.") + if "prompt_config" in req: + res["prompt_config"].update(req["prompt_config"]) + for p in res["prompt_config"]["parameters"]: + if p["optional"]: + continue + if res["prompt_config"]["system"].find("{%s}" % p["key"]) < 0: + return get_error_data_result(retmsg="Parameter '{}' is not used".format(p["key"])) + if "llm_setting" in req: + res["llm_setting"].update(req["llm_setting"]) + req["prompt_config"] = res["prompt_config"] + req["llm_setting"] = res["llm_setting"] + # avatar + if "avatar" in req: + req["icon"] = req.pop("avatar") + if "knowledgebases" in req: + req.pop("knowledgebases") + if not DialogService.update_by_id(chat_id, req): + return get_error_data_result(retmsg="Chat not found!") + return get_result() + + +@manager.route('/chat', methods=['DELETE']) +@token_required +def delete(tenant_id): + req = request.json + ids = req.get("ids") + if not ids: + return get_error_data_result(retmsg="ids are required") + for id in ids: + if not DialogService.query(tenant_id=tenant_id, id=id, status=StatusEnum.VALID.value): + return get_error_data_result(retmsg=f"You don't own the chat {id}") + temp_dict = {"status": StatusEnum.INVALID.value} + DialogService.update_by_id(id, temp_dict) + return get_result() + +@manager.route('/chat', methods=['GET']) +@token_required +def list(tenant_id): + id = request.args.get("id") + name = request.args.get("name") + chat = DialogService.query(id=id,name=name,status=StatusEnum.VALID.value) + if not chat: + return get_error_data_result(retmsg="The chat doesn't exist") + page_number = int(request.args.get("page", 1)) + items_per_page = int(request.args.get("page_size", 1024)) + orderby = request.args.get("orderby", "create_time") + if request.args.get("desc") == "False": + desc = False + else: + desc = True + chats = DialogService.get_list(tenant_id,page_number,items_per_page,orderby,desc,id,name) + if not chats: + return get_result(data=[]) + list_assts = [] + renamed_dict = {} + key_mapping = {"parameters": "variables", + "prologue": "opener", + "quote": "show_quote", + "system": "prompt", + "rerank_id": "rerank_model", + "vector_similarity_weight": "keywords_similarity_weight"} + key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"] + for res in chats: + for key, value in res["prompt_config"].items(): + new_key = key_mapping.get(key, key) + renamed_dict[new_key] = value + res["prompt"] = renamed_dict + del res["prompt_config"] + new_dict = {"similarity_threshold": res["similarity_threshold"], + "keywords_similarity_weight": res["vector_similarity_weight"], + "top_n": res["top_n"], + "rerank_model": res['rerank_id']} + res["prompt"].update(new_dict) + for key in key_list: + del res[key] + res["llm"] = res.pop("llm_setting") + res["llm"]["model_name"] = res.pop("llm_id") + kb_list = [] + for kb_id in res["kb_ids"]: + kb = KnowledgebaseService.query(id=kb_id) + if not kb : + return get_error_data_result(retmsg=f"Don't exist the kb {kb_id}") + kb_list.append(kb[0].to_json()) + del res["kb_ids"] + res["knowledgebases"] = kb_list + res["avatar"] = res.pop("icon") + list_assts.append(res) + return get_result(data=list_assts) diff --git a/api/apps/sdk/dataset.py b/api/apps/sdk/dataset.py index 269edb33af..698e4a9844 100644 --- a/api/apps/sdk/dataset.py +++ b/api/apps/sdk/dataset.py @@ -73,35 +73,24 @@ def create(tenant_id): @token_required def delete(tenant_id): req = request.json - names=req.get("names") ids = req.get("ids") - if not ids and not names: + if not ids: return get_error_data_result( - retmsg="ids or names is required") - id_list=[] - if names: - for name in names: - kbs=KnowledgebaseService.query(name=name,tenant_id=tenant_id) - if not kbs: - return get_error_data_result(retmsg=f"You don't own the dataset {name}") - id_list.append(kbs[0].id) - if ids: - for id in ids: - kbs=KnowledgebaseService.query(id=id,tenant_id=tenant_id) - if not kbs: - return get_error_data_result(retmsg=f"You don't own the dataset {id}") - id_list.extend(ids) - for id in id_list: - for doc in DocumentService.query(kb_id=id): - if not DocumentService.remove_document(doc, tenant_id): - return get_error_data_result( - retmsg="Remove document error.(Database error)") - f2d = File2DocumentService.get_by_document_id(doc.id) - FileService.filter_delete([File.source_type == FileSource.KNOWLEDGEBASE, File.id == f2d[0].file_id]) - File2DocumentService.delete_by_document_id(doc.id) - if not KnowledgebaseService.delete_by_id(id): + retmsg="ids are required") + for id in ids: + kbs = KnowledgebaseService.query(id=id, tenant_id=tenant_id) + if not kbs: + return get_error_data_result(retmsg=f"You don't own the dataset {id}") + for doc in DocumentService.query(kb_id=id): + if not DocumentService.remove_document(doc, tenant_id): return get_error_data_result( - retmsg="Delete dataset error.(Database serror)") + retmsg="Remove document error.(Database error)") + f2d = File2DocumentService.get_by_document_id(doc.id) + FileService.filter_delete([File.source_type == FileSource.KNOWLEDGEBASE, File.id == f2d[0].file_id]) + File2DocumentService.delete_by_document_id(doc.id) + if not KnowledgebaseService.delete_by_id(id): + return get_error_data_result( + retmsg="Delete dataset error.(Database serror)") return get_result(retcode=RetCode.SUCCESS) @manager.route('/dataset/', methods=['PUT']) @@ -161,7 +150,10 @@ def list(tenant_id): page_number = int(request.args.get("page", 1)) items_per_page = int(request.args.get("page_size", 1024)) orderby = request.args.get("orderby", "create_time") - desc = bool(request.args.get("desc", True)) + if request.args.get("desc") == "False": + desc = False + else: + desc = True tenants = TenantService.get_joined_tenants_by_user_id(tenant_id) kbs = KnowledgebaseService.get_list( [m["tenant_id"] for m in tenants], tenant_id, page_number, items_per_page, orderby, desc, id, name) diff --git a/api/db/services/dialog_service.py b/api/db/services/dialog_service.py index e2f513aa08..c7a0e9f252 100644 --- a/api/db/services/dialog_service.py +++ b/api/db/services/dialog_service.py @@ -19,8 +19,8 @@ import re from copy import deepcopy from timeit import default_timer as timer -from api.db import LLMType, ParserType -from api.db.db_models import Dialog, Conversation +from api.db import LLMType, ParserType,StatusEnum +from api.db.db_models import Dialog, Conversation,DB from api.db.services.common_service import CommonService from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.llm_service import LLMService, TenantLLMService, LLMBundle @@ -35,6 +35,28 @@ class DialogService(CommonService): model = Dialog + @classmethod + @DB.connection_context() + def get_list(cls, tenant_id, + page_number, items_per_page, orderby, desc, id , name): + chats = cls.model.select() + if id: + chats = chats.where(cls.model.id == id) + if name: + chats = chats.where(cls.model.name == name) + chats = chats.where( + (cls.model.tenant_id == tenant_id) + & (cls.model.status == StatusEnum.VALID.value) + ) + if desc: + chats = chats.order_by(cls.model.getter_by(orderby).desc()) + else: + chats = chats.order_by(cls.model.getter_by(orderby).asc()) + + chats = chats.paginate(page_number, items_per_page) + + return list(chats.dicts()) + class ConversationService(CommonService): model = Conversation @@ -85,7 +107,7 @@ def llm_id2llm_type(llm_id): for llm in llm_factory["llm"]: if llm_id == llm["llm_name"]: return llm["model_type"].strip(",")[-1] - + def chat(dialog, messages, stream=True, **kwargs): assert messages[-1]["role"] == "user", "The last content of this conversation is not from user." diff --git a/api/http_api.md b/api/http_api.md index 80c1f81a5d..014d2e7305 100644 --- a/api/http_api.md +++ b/api/http_api.md @@ -149,11 +149,11 @@ The error response includes a JSON object like the following: } ``` -## Delete dataset +## Delete datasets **DELETE** `/api/v1/dataset` -Deletes datasets by ids or names. +Deletes datasets by ids. ### Request @@ -163,7 +163,6 @@ Deletes datasets by ids or names. - `content-Type: application/json` - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - Body: - - `"names"`: `List[string]` - `"ids"`: `List[string]` @@ -176,18 +175,15 @@ curl --request DELETE \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \ --data '{ - "names": ["test_1", "test_2"] + "ids": ["test_1", "test_2"] }' ``` #### Request parameters -- `"names"`: (*Body parameter*) - Dataset names to delete. - `"ids"`: (*Body parameter*) Dataset IDs to delete. -`"names"` and `"ids"` are exclusive. ### Response @@ -318,7 +314,7 @@ curl --request GET \ A boolean flag indicating whether the sorting should be in descending order. - `name`: (*Path parameter*) Dataset name -- - `"id"`: (*Path parameter*) +- `"id"`: (*Path parameter*) The ID of the dataset to be retrieved. - `"name"`: (*Path parameter*) The name of the dataset to be retrieved. @@ -996,10 +992,18 @@ Create a chat ### Request - Method: POST -- URL: `/api/v1/chat` +- URL: `http://{address}/api/v1/chat` - Headers: - `content-Type: application/json` - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' +- Body: + - `"name"`: `string` + - `"avatar"`: `string` + - `"knowledgebases"`: `List[DataSet]` + - `"id"`: `string` + - `"llm"`: `LLM` + - `"prompt"`: `Prompt` + #### Request example @@ -1009,135 +1013,424 @@ curl --request POST \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' --data-binary '{ - "avatar": "path", - "create_date": "Wed, 04 Sep 2024 10:08:01 GMT", - "create_time": 1725444481128, - "description": "A helpful Assistant", - "do_refer": "", - "knowledgebases": [ - { - "avatar": null, - "chunk_count": 0, - "description": null, - "document_count": 0, - "embedding_model": "", - "id": "d6d0e8e868cd11ef92250242ac120006", - "language": "English", - "name": "Test_assistant", - "parse_method": "naive", - "parser_config": { - "pages": [ - [ - 1, - 1000000 - ] - ] - }, - "permission": "me", - "tenant_id": "4fb0cd625f9311efba4a0242ac120006" - } - ], - "language": "English", - "llm": { - "frequency_penalty": 0.7, - "max_tokens": 512, - "model_name": "deepseek-chat", - "presence_penalty": 0.4, - "temperature": 0.1, - "top_p": 0.3 - }, - "name": "Miss R", - "prompt": { - "empty_response": "Sorry! Can't find the context!", - "keywords_similarity_weight": 0.7, - "opener": "Hi! I am your assistant, what can I do for you?", - "prompt": "You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence 'The answer you are looking for is not found in the knowledge base!' Answers need to consider chat history.\nHere is the knowledge base:\n{knowledge}\nThe above is the knowledge base.", - "rerank_model": "", - "show_quote": true, - "similarity_threshold": 0.2, - "top_n": 8, - "variables": [ - { - "key": "knowledge", - "optional": true - } - ] - }, - "prompt_type": "simple", - "status": "1", - "top_k": 1024, - "update_date": "Wed, 04 Sep 2024 10:08:01 GMT", - "update_time": 1725444481128 + "knowledgebases": [ + { + "avatar": null, + "chunk_count": 0, + "description": null, + "document_count": 0, + "embedding_model": "", + "id": "0b2cbc8c877f11ef89070242ac120005", + "language": "English", + "name": "Test_assistant", + "parse_method": "naive", + "parser_config": { + "pages": [ + [ + 1, + 1000000 + ] + ] + }, + "permission": "me", + "tenant_id": "4fb0cd625f9311efba4a0242ac120006" + } + ], + "name":"new_chat_1" }' ``` +#### Request parameters + +- `"name"`: (*Body parameter*) + The name of the created chat. + - `"assistant"` + +- `"avatar"`: (*Body parameter*) + The icon of the created chat. + - `"path"` + +- `"knowledgebases"`: (*Body parameter*) + Select knowledgebases associated. + - `["kb1"]` + +- `"id"`: (*Body parameter*) + The id of the created chat. + - `""` + +- `"llm"`: (*Body parameter*) + The LLM of the created chat. + - If the value is `None`, a dictionary with default values will be generated. + +- `"prompt"`: (*Body parameter*) + The prompt of the created chat. + - If the value is `None`, a dictionary with default values will be generated. + +--- + +##### Chat.LLM parameters: + +- `"model_name"`: (*Body parameter*) + Large language chat model. + - If it is `None`, it will return the user's default model. + +- `"temperature"`: (*Body parameter*) + Controls the randomness of predictions by the model. A lower temperature makes the model more confident, while a higher temperature makes it more creative and diverse. + - `0.1` + +- `"top_p"`: (*Body parameter*) + Also known as "nucleus sampling," it focuses on the most likely words, cutting off the less probable ones. + - `0.3` + +- `"presence_penalty"`: (*Body parameter*) + Discourages the model from repeating the same information by penalizing repeated content. + - `0.4` + +- `"frequency_penalty"`: (*Body parameter*) + Reduces the model’s tendency to repeat words frequently. + - `0.7` + +- `"max_tokens"`: (*Body parameter*) + Sets the maximum length of the model’s output, measured in tokens (words or pieces of words). + - `512` + +--- + +##### Chat.Prompt parameters: + +- `"similarity_threshold"`: (*Body parameter*) + Filters out chunks with similarity below this threshold. + - `0.2` + +- `"keywords_similarity_weight"`: (*Body parameter*) + Weighted keywords similarity and vector cosine similarity; the sum of weights is 1.0. + - `0.7` + +- `"top_n"`: (*Body parameter*) + Only the top N chunks above the similarity threshold will be fed to LLMs. + - `8` + +- `"variables"`: (*Body parameter*) + Variables help with different chat strategies by filling in the 'System' part of the prompt. + - `[{"key": "knowledge", "optional": True}]` + +- `"rerank_model"`: (*Body parameter*) + If empty, it uses vector cosine similarity; otherwise, it uses rerank score. + - `""` + +- `"empty_response"`: (*Body parameter*) + If nothing is retrieved, this will be used as the response. Leave blank if LLM should provide its own opinion. + - `None` + +- `"opener"`: (*Body parameter*) + The welcome message for clients. + - `"Hi! I'm your assistant, what can I do for you?"` + +- `"show_quote"`: (*Body parameter*) + Indicates whether the source of the original text should be displayed. + - `True` + +- `"prompt"`: (*Body parameter*) + Instructions for LLM to follow when answering questions, such as character design or answer length. + - `"You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence 'The answer you are looking for is not found in the knowledge base!' Answers need to consider chat history. Here is the knowledge base: {knowledge} The above is the knowledge base."` +### Response +Success: +```json +{ + "code": 0, + "data": { + "avatar": "", + "create_date": "Fri, 11 Oct 2024 03:23:24 GMT", + "create_time": 1728617004635, + "description": "A helpful Assistant", + "do_refer": "1", + "id": "2ca4b22e878011ef88fe0242ac120005", + "knowledgebases": [ + { + "avatar": null, + "chunk_count": 0, + "description": null, + "document_count": 0, + "embedding_model": "", + "id": "0b2cbc8c877f11ef89070242ac120005", + "language": "English", + "name": "Test_assistant", + "parse_method": "naive", + "parser_config": { + "pages": [ + [ + 1, + 1000000 + ] + ] + }, + "permission": "me", + "tenant_id": "4fb0cd625f9311efba4a0242ac120006" + } + ], + "language": "English", + "llm": { + "frequency_penalty": 0.7, + "max_tokens": 512, + "model_name": "deepseek-chat___OpenAI-API@OpenAI-API-Compatible", + "presence_penalty": 0.4, + "temperature": 0.1, + "top_p": 0.3 + }, + "name": "new_chat_1", + "prompt": { + "empty_response": "Sorry! 知识库中未找到相关内容!", + "keywords_similarity_weight": 0.3, + "opener": "您好,我是您的助手小樱,长得可爱又善良,can I help you?", + "prompt": "你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\n 以下是知识库:\n {knowledge}\n 以上是知识库。", + "rerank_model": "", + "similarity_threshold": 0.2, + "top_n": 6, + "variables": [ + { + "key": "knowledge", + "optional": false + } + ] + }, + "prompt_type": "simple", + "status": "1", + "tenant_id": "69736c5e723611efb51b0242ac120007", + "top_k": 1024, + "update_date": "Fri, 11 Oct 2024 03:23:24 GMT", + "update_time": 1728617004635 + } +} +``` +Error: +```json +{ + "code": 102, + "message": "Duplicated chat name in creating dataset." +} +``` + ## Update chat -**PUT** `/api/v1/chat` +**PUT** `/api/v1/chat/{chat_id}` Update a chat ### Request - Method: PUT -- URL: `/api/v1/chat` +- URL: `http://{address}/api/v1/chat/{chat_id}` - Headers: - `content-Type: application/json` - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - +- Body: (Refer to the "Create chat" for the complete structure of the request body.) + #### Request example - +```bash curl --request PUT \ - --url http://{address}/api/v1/chat \ + --url http://{address}/api/v1/chat/{chat_id} \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \ - --data-binary '{ - "id":"554e96746aaa11efb06b0242ac120005", + --data '{ "name":"Test" }' +``` +#### Parameters +(Refer to the "Create chat" for the complete structure of the request parameters.) -## Delete chat +### Response +Success +```json +{ + "code": 0 +} +``` +Error +```json +{ + "code": 102, + "message": "Duplicated chat name in updating dataset." +} +``` + +## Delete chats -**DELETE** `/api/v1/chat/{chat_id}` +**DELETE** `/api/v1/chat` -Delete a chat +Delete chats ### Request -- Method: PUT -- URL: `/api/v1/chat/{chat_id}` +- Method: DELETE +- URL: `http://{address}/api/v1/chat` - Headers: - `content-Type: application/json` - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - +- Body: + - `ids`: List[string] #### Request example - -curl --request PUT \ - --url http://{address}/api/v1/chat/554e96746aaa11efb06b0242ac120005 \ +```bash +# Either id or name must be provided, but not both. +curl --request DELETE \ + --url http://{address}/api/v1/chat \ --header 'Content-Type: application/json' \ - --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' + --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \ + --data '{ + "ids": ["test_1", "test_2"] + }' }' +``` +#### Request parameters: + +- `"ids"`: (*Body parameter*) + IDs of the chats to be deleted. + - `None` +### Response +Success +```json +{ + "code": 0 +} +``` +Error +```json +{ + "code": 102, + "message": "ids are required" +} +``` -## List chat +## List chats -**GET** `/api/v1/chat` +**GET** `/api/v1/chat?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={dataset_name}&id={dataset_id}` -List all chat assistants +List chats based on filter criteria. ### Request - Method: GET -- URL: `/api/v1/chat` +- URL: `http://{address}/api/v1/chat?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={dataset_name}&id={dataset_id}` - Headers: - - `content-Type: application/json` - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' #### Request example +```bash curl --request GET \ - --url http://{address}/api/v1/chat \ - --header 'Content-Type: application/json' \ + --url http://{address}/api/v1/chat?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={dataset_name}&id={dataset_id} \ --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' +``` + +#### Request parameters +- `"page"`: (*Path parameter*) + The current page number to retrieve from the paginated data. This parameter determines which set of records will be fetched. + - `1` + +- `"page_size"`: (*Path parameter*) + The number of records to retrieve per page. This controls how many records will be included in each page. + - `1024` + +- `"orderby"`: (*Path parameter*) + The field by which the records should be sorted. This specifies the attribute or column used to order the results. + - `"create_time"` + +- `"desc"`: (*Path parameter*) + A boolean flag indicating whether the sorting should be in descending order. + - `True` + +- `"id"`: (*Path parameter*) + The ID of the chat to be retrieved. + - `None` + +- `"name"`: (*Path parameter*) + The name of the chat to be retrieved. + - `None` + +### Response +Success +```json +{ + "code": 0, + "data": [ + { + "avatar": "", + "create_date": "Fri, 11 Oct 2024 03:23:24 GMT", + "create_time": 1728617004635, + "description": "A helpful Assistant", + "do_refer": "1", + "id": "2ca4b22e878011ef88fe0242ac120005", + "knowledgebases": [ + { + "avatar": "", + "chunk_num": 0, + "create_date": "Fri, 11 Oct 2024 03:15:18 GMT", + "create_time": 1728616518986, + "created_by": "69736c5e723611efb51b0242ac120007", + "description": "", + "doc_num": 0, + "embd_id": "BAAI/bge-large-zh-v1.5", + "id": "0b2cbc8c877f11ef89070242ac120005", + "language": "English", + "name": "test_delete_chat", + "parser_config": { + "chunk_token_count": 128, + "delimiter": "\n!?。;!?", + "layout_recognize": true, + "task_page_size": 12 + }, + "parser_id": "naive", + "permission": "me", + "similarity_threshold": 0.2, + "status": "1", + "tenant_id": "69736c5e723611efb51b0242ac120007", + "token_num": 0, + "update_date": "Fri, 11 Oct 2024 04:01:31 GMT", + "update_time": 1728619291228, + "vector_similarity_weight": 0.3 + } + ], + "language": "English", + "llm": { + "frequency_penalty": 0.7, + "max_tokens": 512, + "model_name": "deepseek-chat___OpenAI-API@OpenAI-API-Compatible", + "presence_penalty": 0.4, + "temperature": 0.1, + "top_p": 0.3 + }, + "name": "Test", + "prompt": { + "empty_response": "Sorry! 知识库中未找到相关内容!", + "keywords_similarity_weight": 0.3, + "opener": "您好,我是您的助手小樱,长得可爱又善良,can I help you?", + "prompt": "你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\n 以下是知识库:\n {knowledge}\n 以上是知识库。", + "rerank_model": "", + "similarity_threshold": 0.2, + "top_n": 6, + "variables": [ + { + "key": "knowledge", + "optional": false + } + ] + }, + "prompt_type": "simple", + "status": "1", + "tenant_id": "69736c5e723611efb51b0242ac120007", + "top_k": 1024, + "update_date": "Fri, 11 Oct 2024 03:47:58 GMT", + "update_time": 1728618478392 + } + ] +} +``` +Error +```json +{ + "code": 102, + "message": "The chat doesn't exist" +} +``` ## Create a chat session diff --git a/api/python_api_reference.md b/api/python_api_reference.md index 737235580f..f7ecaf911b 100644 --- a/api/python_api_reference.md +++ b/api/python_api_reference.md @@ -107,7 +107,7 @@ ds = rag.create_dataset(name="kb_1") ## Delete knowledge bases ```python -RAGFlow.delete_dataset(ids: List[str] = None, names: List[str] = None) +RAGFlow.delete_datasets(ids: List[str] = None) ``` Deletes knowledge bases. ### Parameters @@ -116,11 +116,7 @@ Deletes knowledge bases. The ids of the datasets to be deleted. -#### names: `List[str]` -The names of the datasets to be deleted. - -Either `ids` or `names` must be provided, but not both. ### Returns ```python @@ -133,8 +129,7 @@ no return from ragflow import RAGFlow rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") -rag.delete_dataset(names=["name_1","name_2"]) -rag.delete_dataset(ids=["id_1","id_2"]) +rag.delete_datasets(ids=["id_1","id_2"]) ``` --- @@ -711,32 +706,35 @@ for c in rag.retrieval(question="What's ragflow?", --- :::tip API GROUPING -Chat assistant APIs +Chat APIs ::: -## Create assistant +## Create chat ```python -RAGFlow.create_assistant( +RAGFlow.create_chat( name: str = "assistant", avatar: str = "path", knowledgebases: List[DataSet] = ["kb1"], - llm: Assistant.LLM = None, - prompt: Assistant.Prompt = None -) -> Assistant + llm: Chat.LLM = None, + prompt: Chat.Prompt = None +) -> Chat + ``` ### Returns -Assistant object. +Chat + +description: assitant object. #### name: `str` -The name of the created assistant. Defaults to `"assistant"`. +The name of the created chat. Defaults to `"assistant"`. #### avatar: `str` -The icon of the created assistant. Defaults to `"path"`. +The icon of the created chat. Defaults to `"path"`. #### knowledgebases: `List[DataSet]` @@ -744,11 +742,11 @@ Select knowledgebases associated. Defaults to `["kb1"]`. #### id: `str` -The id of the created assistant. Defaults to `""`. +The id of the created chat. Defaults to `""`. #### llm: `LLM` -The llm of the created assistant. Defaults to `None`. When the value is `None`, a dictionary with the following values will be generated as the default. +The llm of the created chat. Defaults to `None`. When the value is `None`, a dictionary with the following values will be generated as the default. - **model_name**, `str` Large language chat model. If it is `None`, it will return the user's default model. @@ -782,22 +780,21 @@ from ragflow import RAGFlow rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") kb = rag.get_dataset(name="kb_1") -assi = rag.create_assistant("Miss R", knowledgebases=[kb]) +assi = rag.create_chat("Miss R", knowledgebases=[kb]) ``` --- -## Save updates to a chat assistant +## Update chat ```python -Assistant.save() -> bool +Chat.update(update_message: dict) ``` ### Returns ```python -bool -description:the case of updating an assistant, True or False. +no return ``` ### Examples @@ -807,24 +804,28 @@ from ragflow import RAGFlow rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") kb = rag.get_knowledgebase(name="kb_1") -assi = rag.create_assistant("Miss R", knowledgebases=[kb]) -assi.llm.temperature = 0.8 -assi.save() +assi = rag.create_chat("Miss R", knowledgebases=[kb]) +assi.update({"temperature":0.8}) ``` --- -## Delete assistant +## Delete chats ```python -Assistant.delete() -> bool +RAGFlow.delete_chats(ids: List[str] = None) ``` +### Parameters + +#### ids: `str` + +IDs of the chats to be deleted. + ### Returns ```python -bool -description:the case of deleting an assistant, True or False. +no return ``` ### Examples @@ -833,98 +834,58 @@ description:the case of deleting an assistant, True or False. from ragflow import RAGFlow rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") -kb = rag.get_knowledgebase(name="kb_1") -assi = rag.create_assistant("Miss R", knowledgebases=[kb]) -assi.delete() +rag.delete_chats(ids=["id_1","id_2"]) ``` --- -## Retrieve assistant +## List chats ```python -RAGFlow.get_assistant(id: str = None, name: str = None) -> Assistant +RAGFlow.list_chats( + page: int = 1, + page_size: int = 1024, + orderby: str = "create_time", + desc: bool = True, + id: str = None, + name: str = None +) -> List[Chat] ``` ### Parameters -#### id: `str` - -ID of the assistant to retrieve. If `name` is not provided, `id` is required. - -#### name: `str` - -Name of the assistant to retrieve. If `id` is not provided, `name` is required. - -### Returns - -Assistant object. - -#### name: `str` - -The name of the created assistant. Defaults to `"assistant"`. - -#### avatar: `str` - -The icon of the created assistant. Defaults to `"path"`. - -#### knowledgebases: `List[DataSet]` - -Select knowledgebases associated. Defaults to `["kb1"]`. - -#### id: `str` - -The id of the created assistant. Defaults to `""`. - -#### llm: `LLM` +#### page: `int` -The llm of the created assistant. Defaults to `None`. When the value is `None`, a dictionary with the following values will be generated as the default. - -- **model_name**, `str` - Large language chat model. If it is `None`, it will return the user's default model. -- **temperature**, `float` - This parameter controls the randomness of predictions by the model. A lower temperature makes the model more confident in its responses, while a higher temperature makes it more creative and diverse. Defaults to `0.1`. -- **top_p**, `float` - Also known as “nucleus sampling,” this parameter sets a threshold to select a smaller set of words to sample from. It focuses on the most likely words, cutting off the less probable ones. Defaults to `0.3` -- **presence_penalty**, `float` - This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation. Defaults to `0.2`. -- **frequency penalty**, `float` - Similar to the presence penalty, this reduces the model’s tendency to repeat the same words frequently. Defaults to `0.7`. -- **max_token**, `int` - This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words). Defaults to `512`. +The current page number to retrieve from the paginated data. This parameter determines which set of records will be fetched. +- `1` -#### Prompt: `str` +#### page_size: `int` -Instructions you need LLM to follow when LLM answers questions, like character design, answer length and answer language etc. +The number of records to retrieve per page. This controls how many records will be included in each page. +- `1024` -Defaults: -``` -You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history. - Here is the knowledge base: - {knowledge} - The above is the knowledge base. -``` +#### orderby: `string` -### Examples +The field by which the records should be sorted. This specifies the attribute or column used to order the results. +- `"create_time"` -```python -from ragflow import RAGFlow +#### desc: `bool` -rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") -assi = rag.get_assistant(name="Miss R") -``` +A boolean flag indicating whether the sorting should be in descending order. +- `True` ---- +#### id: `string` -## List assistants +The ID of the chat to be retrieved. +- `None` -```python -RAGFlow.list_assistants() -> List[Assistant] -``` +#### name: `string` +The name of the chat to be retrieved. +- `None` ### Returns -A list of assistant objects. +A list of chat objects. ### Examples @@ -932,7 +893,7 @@ A list of assistant objects. from ragflow import RAGFlow rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") -for assi in rag.list_assistants(): +for assi in rag.list_chats(): print(assi) ``` diff --git a/api/utils/api_utils.py b/api/utils/api_utils.py index 0a91ce04b7..d5748702b1 100644 --- a/api/utils/api_utils.py +++ b/api/utils/api_utils.py @@ -279,8 +279,8 @@ def get_result(retcode=RetCode.SUCCESS, retmsg='error', data=None): response = {"code": retcode, "message": retmsg} return jsonify(response) -def get_error_data_result(retcode=RetCode.DATA_ERROR, - retmsg='Sorry! Data missing!'): +def get_error_data_result(retmsg='Sorry! Data missing!',retcode=RetCode.DATA_ERROR, + ): import re result_dict = { "code": retcode, @@ -295,5 +295,4 @@ def get_error_data_result(retcode=RetCode.DATA_ERROR, continue else: response[key] = value - return jsonify(response) - + return jsonify(response) \ No newline at end of file diff --git a/sdk/python/ragflow/__init__.py b/sdk/python/ragflow/__init__.py index 3f6e4200f7..9f09508a1a 100644 --- a/sdk/python/ragflow/__init__.py +++ b/sdk/python/ragflow/__init__.py @@ -4,7 +4,7 @@ from .ragflow import RAGFlow from .modules.dataset import DataSet -from .modules.assistant import Assistant +from .modules.chat import Chat from .modules.session import Session from .modules.document import Document from .modules.chunk import Chunk \ No newline at end of file diff --git a/sdk/python/ragflow/modules/base.py b/sdk/python/ragflow/modules/base.py index 522eeb1227..eeafe11457 100644 --- a/sdk/python/ragflow/modules/base.py +++ b/sdk/python/ragflow/modules/base.py @@ -18,16 +18,16 @@ def to_json(self): pr[name] = value return pr - def post(self, path, param, stream=False): - res = self.rag.post(path, param, stream=stream) + def post(self, path, json, stream=False): + res = self.rag.post(path, json, stream=stream) return res def get(self, path, params): res = self.rag.get(path, params) return res - def rm(self, path, params): - res = self.rag.delete(path, params) + def rm(self, path, json): + res = self.rag.delete(path, json) return res def put(self,path, json): diff --git a/sdk/python/ragflow/modules/assistant.py b/sdk/python/ragflow/modules/chat.py similarity index 78% rename from sdk/python/ragflow/modules/assistant.py rename to sdk/python/ragflow/modules/chat.py index 8be68d1ea5..0b12097a8c 100644 --- a/sdk/python/ragflow/modules/assistant.py +++ b/sdk/python/ragflow/modules/chat.py @@ -4,14 +4,14 @@ from .session import Session -class Assistant(Base): +class Chat(Base): def __init__(self, rag, res_dict): self.id = "" self.name = "assistant" self.avatar = "path/to/avatar" self.knowledgebases = ["kb1"] - self.llm = Assistant.LLM(rag, {}) - self.prompt = Assistant.Prompt(rag, {}) + self.llm = Chat.LLM(rag, {}) + self.prompt = Chat.Prompt(rag, {}) super().__init__(rag, res_dict) class LLM(Base): @@ -42,21 +42,13 @@ def __init__(self, rag, res_dict): ) super().__init__(rag, res_dict) - def save(self) -> bool: - res = self.post('/assistant/save', - {"id": self.id, "name": self.name, "avatar": self.avatar, "knowledgebases": self.knowledgebases, - "llm": self.llm.to_json(), "prompt": self.prompt.to_json() - }) + def update(self, update_message: dict): + res = self.put(f'/chat/{self.id}', + update_message) res = res.json() - if res.get("retmsg") == "success": return True - raise Exception(res["retmsg"]) + if res.get("code") != 0: + raise Exception(res["message"]) - def delete(self) -> bool: - res = self.rm('/assistant/delete', - {"id": self.id}) - res = res.json() - if res.get("retmsg") == "success": return True - raise Exception(res["retmsg"]) def create_session(self, name: str = "New session") -> Session: res = self.post("/session/save", {"name": name, "assistant_id": self.id}) diff --git a/sdk/python/ragflow/ragflow.py b/sdk/python/ragflow/ragflow.py index 9c0d7327bb..990e31c650 100644 --- a/sdk/python/ragflow/ragflow.py +++ b/sdk/python/ragflow/ragflow.py @@ -17,7 +17,7 @@ import requests -from .modules.assistant import Assistant +from .modules.chat import Chat from .modules.chunk import Chunk from .modules.dataset import DataSet from .modules.document import Document @@ -32,16 +32,16 @@ def __init__(self, user_key, base_url, version='v1'): self.api_url = f"{base_url}/api/{version}" self.authorization_header = {"Authorization": "{} {}".format("Bearer", self.user_key)} - def post(self, path, param, stream=False): - res = requests.post(url=self.api_url + path, json=param, headers=self.authorization_header, stream=stream) + def post(self, path, json, stream=False): + res = requests.post(url=self.api_url + path, json=json, headers=self.authorization_header, stream=stream) return res def get(self, path, params=None): res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header) return res - def delete(self, path, params): - res = requests.delete(url=self.api_url + path, json=params, headers=self.authorization_header) + def delete(self, path, json): + res = requests.delete(url=self.api_url + path, json=json, headers=self.authorization_header) return res def put(self, path, json): @@ -68,7 +68,7 @@ def create_dataset(self, name: str, avatar: str = "", description: str = "", lan return DataSet(self, res["data"]) raise Exception(res["message"]) - def delete_dataset(self, ids: List[str] = None, names: List[str] = None): + def delete_datasets(self, ids: List[str] = None, names: List[str] = None): res = self.delete("/dataset",{"ids": ids, "names": names}) res=res.json() if res.get("code") != 0: @@ -87,21 +87,21 @@ def list_datasets(self, page: int = 1, page_size: int = 1024, orderby: str = "cr return result_list raise Exception(res["message"]) - def create_assistant(self, name: str = "assistant", avatar: str = "path", knowledgebases: List[DataSet] = [], - llm: Assistant.LLM = None, prompt: Assistant.Prompt = None) -> Assistant: + def create_chat(self, name: str = "assistant", avatar: str = "path", knowledgebases: List[DataSet] = [], + llm: Chat.LLM = None, prompt: Chat.Prompt = None) -> Chat: datasets = [] for dataset in knowledgebases: datasets.append(dataset.to_json()) if llm is None: - llm = Assistant.LLM(self, {"model_name": None, + llm = Chat.LLM(self, {"model_name": None, "temperature": 0.1, "top_p": 0.3, "presence_penalty": 0.4, "frequency_penalty": 0.7, "max_tokens": 512, }) if prompt is None: - prompt = Assistant.Prompt(self, {"similarity_threshold": 0.2, + prompt = Chat.Prompt(self, {"similarity_threshold": 0.2, "keywords_similarity_weight": 0.7, "top_n": 8, "variables": [{ @@ -127,28 +127,29 @@ def create_assistant(self, name: str = "assistant", avatar: str = "path", knowle "knowledgebases": datasets, "llm": llm.to_json(), "prompt": prompt.to_json()} - res = self.post("/assistant/save", temp_dict) + res = self.post("/chat", temp_dict) res = res.json() - if res.get("retmsg") == "success": - return Assistant(self, res["data"]) - raise Exception(res["retmsg"]) + if res.get("code") == 0: + return Chat(self, res["data"]) + raise Exception(res["message"]) - def get_assistant(self, id: str = None, name: str = None) -> Assistant: - res = self.get("/assistant/get", {"id": id, "name": name}) + def delete_chats(self,ids: List[str] = None,names: List[str] = None ) -> bool: + res = self.delete('/chat', + {"ids":ids, "names":names}) res = res.json() - if res.get("retmsg") == "success": - return Assistant(self, res['data']) - raise Exception(res["retmsg"]) + if res.get("code") != 0: + raise Exception(res["message"]) - def list_assistants(self) -> List[Assistant]: - res = self.get("/assistant/list") + def list_chats(self, page: int = 1, page_size: int = 1024, orderby: str = "create_time", desc: bool = True, + id: str = None, name: str = None) -> List[Chat]: + res = self.get("/chat",{"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id, "name": name}) res = res.json() result_list = [] - if res.get("retmsg") == "success": + if res.get("code") == 0: for data in res['data']: - result_list.append(Assistant(self, data)) + result_list.append(Chat(self, data)) return result_list - raise Exception(res["retmsg"]) + raise Exception(res["message"]) def create_document(self, ds: DataSet, name: str, blob: bytes) -> bool: url = f"/doc/dataset/{ds.id}/documents/upload" diff --git a/sdk/python/test/t_assistant.py b/sdk/python/test/t_assistant.py deleted file mode 100644 index ef91f5d781..0000000000 --- a/sdk/python/test/t_assistant.py +++ /dev/null @@ -1,68 +0,0 @@ -from ragflow import RAGFlow, Assistant - -from common import API_KEY, HOST_ADDRESS -from test_sdkbase import TestSdk - - -class TestAssistant(TestSdk): - def test_create_assistant_with_success(self): - """ - Test creating an assistant with success - """ - rag = RAGFlow(API_KEY, HOST_ADDRESS) - kb = rag.create_dataset(name="test_create_assistant") - assistant = rag.create_assistant("test_create", knowledgebases=[kb]) - if isinstance(assistant, Assistant): - assert assistant.name == "test_create", "Name does not match." - else: - assert False, f"Failed to create assistant, error: {assistant}" - - def test_update_assistant_with_success(self): - """ - Test updating an assistant with success. - """ - rag = RAGFlow(API_KEY, HOST_ADDRESS) - kb = rag.create_dataset(name="test_update_assistant") - assistant = rag.create_assistant("test_update", knowledgebases=[kb]) - if isinstance(assistant, Assistant): - assert assistant.name == "test_update", "Name does not match." - assistant.name = 'new_assistant' - res = assistant.save() - assert res is True, f"Failed to update assistant, error: {res}" - else: - assert False, f"Failed to create assistant, error: {assistant}" - - def test_delete_assistant_with_success(self): - """ - Test deleting an assistant with success - """ - rag = RAGFlow(API_KEY, HOST_ADDRESS) - kb = rag.create_dataset(name="test_delete_assistant") - assistant = rag.create_assistant("test_delete", knowledgebases=[kb]) - if isinstance(assistant, Assistant): - assert assistant.name == "test_delete", "Name does not match." - res = assistant.delete() - assert res is True, f"Failed to delete assistant, error: {res}" - else: - assert False, f"Failed to create assistant, error: {assistant}" - - def test_list_assistants_with_success(self): - """ - Test listing assistants with success - """ - rag = RAGFlow(API_KEY, HOST_ADDRESS) - list_assistants = rag.list_assistants() - assert len(list_assistants) > 0, "Do not exist any assistant" - for assistant in list_assistants: - assert isinstance(assistant, Assistant), "Existence type is not assistant." - - def test_get_detail_assistant_with_success(self): - """ - Test getting an assistant's detail with success - """ - rag = RAGFlow(API_KEY, HOST_ADDRESS) - kb = rag.create_dataset(name="test_get_assistant") - rag.create_assistant("test_get_assistant", knowledgebases=[kb]) - assistant = rag.get_assistant(name="test_get_assistant") - assert isinstance(assistant, Assistant), f"Failed to get assistant, error: {assistant}." - assert assistant.name == "test_get_assistant", "Name does not match" diff --git a/sdk/python/test/t_chat.py b/sdk/python/test/t_chat.py new file mode 100644 index 0000000000..93a55fa454 --- /dev/null +++ b/sdk/python/test/t_chat.py @@ -0,0 +1,56 @@ +from ragflow import RAGFlow, Chat + +from common import API_KEY, HOST_ADDRESS +from test_sdkbase import TestSdk + + +class TestChat(TestSdk): + def test_create_chat_with_success(self): + """ + Test creating an chat with success + """ + rag = RAGFlow(API_KEY, HOST_ADDRESS) + kb = rag.create_dataset(name="test_create_chat") + chat = rag.create_chat("test_create", knowledgebases=[kb]) + if isinstance(chat, Chat): + assert chat.name == "test_create", "Name does not match." + else: + assert False, f"Failed to create chat, error: {chat}" + + def test_update_chat_with_success(self): + """ + Test updating an chat with success. + """ + rag = RAGFlow(API_KEY, HOST_ADDRESS) + kb = rag.create_dataset(name="test_update_chat") + chat = rag.create_chat("test_update", knowledgebases=[kb]) + if isinstance(chat, Chat): + assert chat.name == "test_update", "Name does not match." + res=chat.update({"name":"new_chat"}) + assert res is None, f"Failed to update chat, error: {res}" + else: + assert False, f"Failed to create chat, error: {chat}" + + def test_delete_chats_with_success(self): + """ + Test deleting an chat with success + """ + rag = RAGFlow(API_KEY, HOST_ADDRESS) + kb = rag.create_dataset(name="test_delete_chat") + chat = rag.create_chat("test_delete", knowledgebases=[kb]) + if isinstance(chat, Chat): + assert chat.name == "test_delete", "Name does not match." + res = rag.delete_chats(ids=[chat.id]) + assert res is None, f"Failed to delete chat, error: {res}" + else: + assert False, f"Failed to create chat, error: {chat}" + + def test_list_chats_with_success(self): + """ + Test listing chats with success + """ + rag = RAGFlow(API_KEY, HOST_ADDRESS) + list_chats = rag.list_chats() + assert len(list_chats) > 0, "Do not exist any chat" + for chat in list_chats: + assert isinstance(chat, Chat), "Existence type is not chat." diff --git a/sdk/python/test/t_dataset.py b/sdk/python/test/t_dataset.py index c995613faf..db5ed7e63e 100644 --- a/sdk/python/test/t_dataset.py +++ b/sdk/python/test/t_dataset.py @@ -29,7 +29,7 @@ def test_update_dataset_with_success(self): else: assert False, f"Failed to create dataset, error: {ds}" - def test_delete_dataset_with_success(self): + def test_delete_datasets_with_success(self): """ Test deleting a dataset with success """ @@ -37,7 +37,7 @@ def test_delete_dataset_with_success(self): ds = rag.create_dataset("MA") if isinstance(ds, DataSet): assert ds.name == "MA", "Name does not match." - res = rag.delete_dataset(names=["MA"]) + res = rag.delete_datasets(ids=[ds.id]) assert res is None, f"Failed to delete dataset, error: {res}" else: assert False, f"Failed to create dataset, error: {ds}"