Refactor Chat API (#2804)

### What problem does this PR solve?

Refactor Chat API

### Type of change

- [x] Refactoring

---------

Co-authored-by: liuhua <10215101452@stu.ecun.edu.cn>
This commit is contained in:
liuhua 2024-10-12 13:48:43 +08:00 committed by GitHub
parent 2a86472b88
commit a20b82092f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 883 additions and 652 deletions

View File

@ -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)

287
api/apps/sdk/chat.py Normal file
View File

@ -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/<chat_id>', 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)

View File

@ -73,35 +73,24 @@ def create(tenant_id):
@token_required @token_required
def delete(tenant_id): def delete(tenant_id):
req = request.json req = request.json
names=req.get("names")
ids = req.get("ids") ids = req.get("ids")
if not ids and not names: if not ids:
return get_error_data_result( return get_error_data_result(
retmsg="ids or names is required") retmsg="ids are required")
id_list=[] for id in ids:
if names: kbs = KnowledgebaseService.query(id=id, tenant_id=tenant_id)
for name in names: if not kbs:
kbs=KnowledgebaseService.query(name=name,tenant_id=tenant_id) return get_error_data_result(retmsg=f"You don't own the dataset {id}")
if not kbs: for doc in DocumentService.query(kb_id=id):
return get_error_data_result(retmsg=f"You don't own the dataset {name}") if not DocumentService.remove_document(doc, tenant_id):
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):
return get_error_data_result( 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) return get_result(retcode=RetCode.SUCCESS)
@manager.route('/dataset/<dataset_id>', methods=['PUT']) @manager.route('/dataset/<dataset_id>', methods=['PUT'])
@ -161,7 +150,10 @@ def list(tenant_id):
page_number = int(request.args.get("page", 1)) page_number = int(request.args.get("page", 1))
items_per_page = int(request.args.get("page_size", 1024)) items_per_page = int(request.args.get("page_size", 1024))
orderby = request.args.get("orderby", "create_time") 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) tenants = TenantService.get_joined_tenants_by_user_id(tenant_id)
kbs = KnowledgebaseService.get_list( kbs = KnowledgebaseService.get_list(
[m["tenant_id"] for m in tenants], tenant_id, page_number, items_per_page, orderby, desc, id, name) [m["tenant_id"] for m in tenants], tenant_id, page_number, items_per_page, orderby, desc, id, name)

View File

@ -19,8 +19,8 @@ import json
import re import re
from copy import deepcopy from copy import deepcopy
from timeit import default_timer as timer from timeit import default_timer as timer
from api.db import LLMType, ParserType from api.db import LLMType, ParserType,StatusEnum
from api.db.db_models import Dialog, Conversation from api.db.db_models import Dialog, Conversation,DB
from api.db.services.common_service import CommonService from api.db.services.common_service import CommonService
from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMService, TenantLLMService, LLMBundle from api.db.services.llm_service import LLMService, TenantLLMService, LLMBundle
@ -35,6 +35,28 @@ from api.utils.file_utils import get_project_base_directory
class DialogService(CommonService): class DialogService(CommonService):
model = Dialog 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): class ConversationService(CommonService):
model = Conversation model = Conversation
@ -85,7 +107,7 @@ def llm_id2llm_type(llm_id):
for llm in llm_factory["llm"]: for llm in llm_factory["llm"]:
if llm_id == llm["llm_name"]: if llm_id == llm["llm_name"]:
return llm["model_type"].strip(",")[-1] return llm["model_type"].strip(",")[-1]
def chat(dialog, messages, stream=True, **kwargs): def chat(dialog, messages, stream=True, **kwargs):
assert messages[-1]["role"] == "user", "The last content of this conversation is not from user." assert messages[-1]["role"] == "user", "The last content of this conversation is not from user."

View File

@ -149,11 +149,11 @@ The error response includes a JSON object like the following:
} }
``` ```
## Delete dataset ## Delete datasets
**DELETE** `/api/v1/dataset` **DELETE** `/api/v1/dataset`
Deletes datasets by ids or names. Deletes datasets by ids.
### Request ### Request
@ -163,7 +163,6 @@ Deletes datasets by ids or names.
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body: - Body:
- `"names"`: `List[string]`
- `"ids"`: `List[string]` - `"ids"`: `List[string]`
@ -176,18 +175,15 @@ curl --request DELETE \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \ --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \
--data '{ --data '{
"names": ["test_1", "test_2"] "ids": ["test_1", "test_2"]
}' }'
``` ```
#### Request parameters #### Request parameters
- `"names"`: (*Body parameter*)
Dataset names to delete.
- `"ids"`: (*Body parameter*) - `"ids"`: (*Body parameter*)
Dataset IDs to delete. Dataset IDs to delete.
`"names"` and `"ids"` are exclusive.
### Response ### Response
@ -318,7 +314,7 @@ curl --request GET \
A boolean flag indicating whether the sorting should be in descending order. A boolean flag indicating whether the sorting should be in descending order.
- `name`: (*Path parameter*) - `name`: (*Path parameter*)
Dataset name Dataset name
- - `"id"`: (*Path parameter*) - `"id"`: (*Path parameter*)
The ID of the dataset to be retrieved. The ID of the dataset to be retrieved.
- `"name"`: (*Path parameter*) - `"name"`: (*Path parameter*)
The name of the dataset to be retrieved. The name of the dataset to be retrieved.
@ -996,10 +992,18 @@ Create a chat
### Request ### Request
- Method: POST - Method: POST
- URL: `/api/v1/chat` - URL: `http://{address}/api/v1/chat`
- Headers: - Headers:
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body:
- `"name"`: `string`
- `"avatar"`: `string`
- `"knowledgebases"`: `List[DataSet]`
- `"id"`: `string`
- `"llm"`: `LLM`
- `"prompt"`: `Prompt`
#### Request example #### Request example
@ -1009,135 +1013,424 @@ curl --request POST \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
--data-binary '{ --data-binary '{
"avatar": "path", "knowledgebases": [
"create_date": "Wed, 04 Sep 2024 10:08:01 GMT", {
"create_time": 1725444481128, "avatar": null,
"description": "A helpful Assistant", "chunk_count": 0,
"do_refer": "", "description": null,
"knowledgebases": [ "document_count": 0,
{ "embedding_model": "",
"avatar": null, "id": "0b2cbc8c877f11ef89070242ac120005",
"chunk_count": 0, "language": "English",
"description": null, "name": "Test_assistant",
"document_count": 0, "parse_method": "naive",
"embedding_model": "", "parser_config": {
"id": "d6d0e8e868cd11ef92250242ac120006", "pages": [
"language": "English", [
"name": "Test_assistant", 1,
"parse_method": "naive", 1000000
"parser_config": { ]
"pages": [ ]
[ },
1, "permission": "me",
1000000 "tenant_id": "4fb0cd625f9311efba4a0242ac120006"
] }
] ],
}, "name":"new_chat_1"
"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
}' }'
``` ```
#### 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 models tendency to repeat words frequently.
- `0.7`
- `"max_tokens"`: (*Body parameter*)
Sets the maximum length of the models 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 ## Update chat
**PUT** `/api/v1/chat` **PUT** `/api/v1/chat/{chat_id}`
Update a chat Update a chat
### Request ### Request
- Method: PUT - Method: PUT
- URL: `/api/v1/chat` - URL: `http://{address}/api/v1/chat/{chat_id}`
- Headers: - Headers:
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body: (Refer to the "Create chat" for the complete structure of the request body.)
#### Request example #### Request example
```bash
curl --request PUT \ curl --request PUT \
--url http://{address}/api/v1/chat \ --url http://{address}/api/v1/chat/{chat_id} \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \ --header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' \
--data-binary '{ --data '{
"id":"554e96746aaa11efb06b0242ac120005",
"name":"Test" "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** `/api/v1/chat/{chat_id}` ## Delete chats
Delete a chat **DELETE** `/api/v1/chat`
Delete chats
### Request ### Request
- Method: PUT - Method: DELETE
- URL: `/api/v1/chat/{chat_id}` - URL: `http://{address}/api/v1/chat`
- Headers: - Headers:
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body:
- `ids`: List[string]
#### Request example #### Request example
```bash
curl --request PUT \ # Either id or name must be provided, but not both.
--url http://{address}/api/v1/chat/554e96746aaa11efb06b0242ac120005 \ curl --request DELETE \
--url http://{address}/api/v1/chat \
--header 'Content-Type: application/json' \ --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:
## List chat - `"ids"`: (*Body parameter*)
IDs of the chats to be deleted.
- `None`
### Response
Success
```json
{
"code": 0
}
```
Error
```json
{
"code": 102,
"message": "ids are required"
}
```
**GET** `/api/v1/chat` ## List chats
List all chat assistants **GET** `/api/v1/chat?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={dataset_name}&id={dataset_id}`
List chats based on filter criteria.
### Request ### Request
- Method: GET - 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: - Headers:
- `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
#### Request example #### Request example
```bash
curl --request GET \ curl --request GET \
--url http://{address}/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} \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' --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 ## Create a chat session

View File

@ -107,7 +107,7 @@ ds = rag.create_dataset(name="kb_1")
## Delete knowledge bases ## Delete knowledge bases
```python ```python
RAGFlow.delete_dataset(ids: List[str] = None, names: List[str] = None) RAGFlow.delete_datasets(ids: List[str] = None)
``` ```
Deletes knowledge bases. Deletes knowledge bases.
### Parameters ### Parameters
@ -116,11 +116,7 @@ Deletes knowledge bases.
The ids of the datasets to be deleted. 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 ### Returns
```python ```python
@ -133,8 +129,7 @@ no return
from ragflow import RAGFlow from ragflow import RAGFlow
rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
rag.delete_dataset(names=["name_1","name_2"]) rag.delete_datasets(ids=["id_1","id_2"])
rag.delete_dataset(ids=["id_1","id_2"])
``` ```
--- ---
@ -711,32 +706,35 @@ for c in rag.retrieval(question="What's ragflow?",
--- ---
:::tip API GROUPING :::tip API GROUPING
Chat assistant APIs Chat APIs
::: :::
## Create assistant ## Create chat
```python ```python
RAGFlow.create_assistant( RAGFlow.create_chat(
name: str = "assistant", name: str = "assistant",
avatar: str = "path", avatar: str = "path",
knowledgebases: List[DataSet] = ["kb1"], knowledgebases: List[DataSet] = ["kb1"],
llm: Assistant.LLM = None, llm: Chat.LLM = None,
prompt: Assistant.Prompt = None prompt: Chat.Prompt = None
) -> Assistant ) -> Chat
``` ```
### Returns ### Returns
Assistant object. Chat
description: assitant object.
#### name: `str` #### name: `str`
The name of the created assistant. Defaults to `"assistant"`. The name of the created chat. Defaults to `"assistant"`.
#### avatar: `str` #### avatar: `str`
The icon of the created assistant. Defaults to `"path"`. The icon of the created chat. Defaults to `"path"`.
#### knowledgebases: `List[DataSet]` #### knowledgebases: `List[DataSet]`
@ -744,11 +742,11 @@ Select knowledgebases associated. Defaults to `["kb1"]`.
#### id: `str` #### id: `str`
The id of the created assistant. Defaults to `""`. The id of the created chat. Defaults to `""`.
#### llm: `LLM` #### 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` - **model_name**, `str`
Large language chat model. If it is `None`, it will return the user's default model. 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") rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
kb = rag.get_dataset(name="kb_1") 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 ```python
Assistant.save() -> bool Chat.update(update_message: dict)
``` ```
### Returns ### Returns
```python ```python
bool no return
description:the case of updating an assistant, True or False.
``` ```
### Examples ### Examples
@ -807,24 +804,28 @@ from ragflow import RAGFlow
rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
kb = rag.get_knowledgebase(name="kb_1") kb = rag.get_knowledgebase(name="kb_1")
assi = rag.create_assistant("Miss R" knowledgebases=[kb]) assi = rag.create_chat("Miss R" knowledgebases=[kb])
assi.llm.temperature = 0.8 assi.update({"temperature":0.8})
assi.save()
``` ```
--- ---
## Delete assistant ## Delete chats
```python ```python
Assistant.delete() -> bool RAGFlow.delete_chats(ids: List[str] = None)
``` ```
### Parameters
#### ids: `str`
IDs of the chats to be deleted.
### Returns ### Returns
```python ```python
bool no return
description:the case of deleting an assistant, True or False.
``` ```
### Examples ### Examples
@ -833,77 +834,58 @@ description:the case of deleting an assistant, True or False.
from ragflow import RAGFlow from ragflow import RAGFlow
rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
kb = rag.get_knowledgebase(name="kb_1") rag.delete_chats(ids=["id_1","id_2"])
assi = rag.create_assistant("Miss R" knowledgebases=[kb])
assi.delete()
``` ```
--- ---
## Retrieve assistant ## List chats
```python ```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 ### Parameters
#### id: `str` #### page: `int`
ID of the assistant to retrieve. If `name` is not provided, `id` is required. The current page number to retrieve from the paginated data. This parameter determines which set of records will be fetched.
- `1`
#### name: `str` #### page_size: `int`
Name of the assistant to retrieve. If `id` is not provided, `name` is required. The number of records to retrieve per page. This controls how many records will be included in each page.
- `1024`
#### orderby: `string`
The field by which the records should be sorted. This specifies the attribute or column used to order the results.
- `"create_time"`
#### desc: `bool`
A boolean flag indicating whether the sorting should be in descending order.
- `True`
#### id: `string`
The ID of the chat to be retrieved.
- `None`
#### name: `string`
The name of the chat to be retrieved.
- `None`
### Returns ### Returns
Assistant object. A list of chat objects.
#### 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`
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 models tendency to repeat the same words frequently. Defaults to `0.7`.
- **max_token**, `int`
This sets the maximum length of the models output, measured in the number of tokens (words or pieces of words). Defaults to `512`.
#### Prompt: `str`
Instructions you need LLM to follow when LLM answers questions, like character design, answer length and answer language etc.
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.
```
### Examples ### Examples
@ -911,28 +893,7 @@ You are an intelligent assistant. Please summarize the content of the knowledge
from ragflow import RAGFlow from ragflow import RAGFlow
rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380") rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
assi = rag.get_assistant(name="Miss R") for assi in rag.list_chats():
```
---
## List assistants
```python
RAGFlow.list_assistants() -> List[Assistant]
```
### Returns
A list of assistant objects.
### Examples
```python
from ragflow import RAGFlow
rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
for assi in rag.list_assistants():
print(assi) print(assi)
``` ```

View File

@ -279,8 +279,8 @@ def get_result(retcode=RetCode.SUCCESS, retmsg='error', data=None):
response = {"code": retcode, "message": retmsg} response = {"code": retcode, "message": retmsg}
return jsonify(response) return jsonify(response)
def get_error_data_result(retcode=RetCode.DATA_ERROR, def get_error_data_result(retmsg='Sorry! Data missing!',retcode=RetCode.DATA_ERROR,
retmsg='Sorry! Data missing!'): ):
import re import re
result_dict = { result_dict = {
"code": retcode, "code": retcode,
@ -295,5 +295,4 @@ def get_error_data_result(retcode=RetCode.DATA_ERROR,
continue continue
else: else:
response[key] = value response[key] = value
return jsonify(response) return jsonify(response)

View File

@ -4,7 +4,7 @@ __version__ = importlib.metadata.version("ragflow")
from .ragflow import RAGFlow from .ragflow import RAGFlow
from .modules.dataset import DataSet from .modules.dataset import DataSet
from .modules.assistant import Assistant from .modules.chat import Chat
from .modules.session import Session from .modules.session import Session
from .modules.document import Document from .modules.document import Document
from .modules.chunk import Chunk from .modules.chunk import Chunk

View File

@ -18,16 +18,16 @@ class Base(object):
pr[name] = value pr[name] = value
return pr return pr
def post(self, path, param, stream=False): def post(self, path, json, stream=False):
res = self.rag.post(path, param, stream=stream) res = self.rag.post(path, json, stream=stream)
return res return res
def get(self, path, params): def get(self, path, params):
res = self.rag.get(path, params) res = self.rag.get(path, params)
return res return res
def rm(self, path, params): def rm(self, path, json):
res = self.rag.delete(path, params) res = self.rag.delete(path, json)
return res return res
def put(self,path, json): def put(self,path, json):

View File

@ -4,14 +4,14 @@ from .base import Base
from .session import Session from .session import Session
class Assistant(Base): class Chat(Base):
def __init__(self, rag, res_dict): def __init__(self, rag, res_dict):
self.id = "" self.id = ""
self.name = "assistant" self.name = "assistant"
self.avatar = "path/to/avatar" self.avatar = "path/to/avatar"
self.knowledgebases = ["kb1"] self.knowledgebases = ["kb1"]
self.llm = Assistant.LLM(rag, {}) self.llm = Chat.LLM(rag, {})
self.prompt = Assistant.Prompt(rag, {}) self.prompt = Chat.Prompt(rag, {})
super().__init__(rag, res_dict) super().__init__(rag, res_dict)
class LLM(Base): class LLM(Base):
@ -42,21 +42,13 @@ class Assistant(Base):
) )
super().__init__(rag, res_dict) super().__init__(rag, res_dict)
def save(self) -> bool: def update(self, update_message: dict):
res = self.post('/assistant/save', res = self.put(f'/chat/{self.id}',
{"id": self.id, "name": self.name, "avatar": self.avatar, "knowledgebases": self.knowledgebases, update_message)
"llm": self.llm.to_json(), "prompt": self.prompt.to_json()
})
res = res.json() res = res.json()
if res.get("retmsg") == "success": return True if res.get("code") != 0:
raise Exception(res["retmsg"]) 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: def create_session(self, name: str = "New session") -> Session:
res = self.post("/session/save", {"name": name, "assistant_id": self.id}) res = self.post("/session/save", {"name": name, "assistant_id": self.id})

View File

@ -17,7 +17,7 @@ from typing import List
import requests import requests
from .modules.assistant import Assistant from .modules.chat import Chat
from .modules.chunk import Chunk from .modules.chunk import Chunk
from .modules.dataset import DataSet from .modules.dataset import DataSet
from .modules.document import Document from .modules.document import Document
@ -32,16 +32,16 @@ class RAGFlow:
self.api_url = f"{base_url}/api/{version}" self.api_url = f"{base_url}/api/{version}"
self.authorization_header = {"Authorization": "{} {}".format("Bearer", self.user_key)} self.authorization_header = {"Authorization": "{} {}".format("Bearer", self.user_key)}
def post(self, path, param, stream=False): def post(self, path, json, stream=False):
res = requests.post(url=self.api_url + path, json=param, headers=self.authorization_header, stream=stream) res = requests.post(url=self.api_url + path, json=json, headers=self.authorization_header, stream=stream)
return res return res
def get(self, path, params=None): def get(self, path, params=None):
res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header) res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header)
return res return res
def delete(self, path, params): def delete(self, path, json):
res = requests.delete(url=self.api_url + path, json=params, headers=self.authorization_header) res = requests.delete(url=self.api_url + path, json=json, headers=self.authorization_header)
return res return res
def put(self, path, json): def put(self, path, json):
@ -68,7 +68,7 @@ class RAGFlow:
return DataSet(self, res["data"]) return DataSet(self, res["data"])
raise Exception(res["message"]) 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 = self.delete("/dataset",{"ids": ids, "names": names})
res=res.json() res=res.json()
if res.get("code") != 0: if res.get("code") != 0:
@ -87,21 +87,21 @@ class RAGFlow:
return result_list return result_list
raise Exception(res["message"]) raise Exception(res["message"])
def create_assistant(self, name: str = "assistant", avatar: str = "path", knowledgebases: List[DataSet] = [], def create_chat(self, name: str = "assistant", avatar: str = "path", knowledgebases: List[DataSet] = [],
llm: Assistant.LLM = None, prompt: Assistant.Prompt = None) -> Assistant: llm: Chat.LLM = None, prompt: Chat.Prompt = None) -> Chat:
datasets = [] datasets = []
for dataset in knowledgebases: for dataset in knowledgebases:
datasets.append(dataset.to_json()) datasets.append(dataset.to_json())
if llm is None: if llm is None:
llm = Assistant.LLM(self, {"model_name": None, llm = Chat.LLM(self, {"model_name": None,
"temperature": 0.1, "temperature": 0.1,
"top_p": 0.3, "top_p": 0.3,
"presence_penalty": 0.4, "presence_penalty": 0.4,
"frequency_penalty": 0.7, "frequency_penalty": 0.7,
"max_tokens": 512, }) "max_tokens": 512, })
if prompt is None: 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, "keywords_similarity_weight": 0.7,
"top_n": 8, "top_n": 8,
"variables": [{ "variables": [{
@ -127,28 +127,29 @@ class RAGFlow:
"knowledgebases": datasets, "knowledgebases": datasets,
"llm": llm.to_json(), "llm": llm.to_json(),
"prompt": prompt.to_json()} "prompt": prompt.to_json()}
res = self.post("/assistant/save", temp_dict) res = self.post("/chat", temp_dict)
res = res.json() res = res.json()
if res.get("retmsg") == "success": if res.get("code") == 0:
return Assistant(self, res["data"]) return Chat(self, res["data"])
raise Exception(res["retmsg"]) raise Exception(res["message"])
def get_assistant(self, id: str = None, name: str = None) -> Assistant: def delete_chats(self,ids: List[str] = None,names: List[str] = None ) -> bool:
res = self.get("/assistant/get", {"id": id, "name": name}) res = self.delete('/chat',
{"ids":ids, "names":names})
res = res.json() res = res.json()
if res.get("retmsg") == "success": if res.get("code") != 0:
return Assistant(self, res['data']) raise Exception(res["message"])
raise Exception(res["retmsg"])
def list_assistants(self) -> List[Assistant]: def list_chats(self, page: int = 1, page_size: int = 1024, orderby: str = "create_time", desc: bool = True,
res = self.get("/assistant/list") 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() res = res.json()
result_list = [] result_list = []
if res.get("retmsg") == "success": if res.get("code") == 0:
for data in res['data']: for data in res['data']:
result_list.append(Assistant(self, data)) result_list.append(Chat(self, data))
return result_list return result_list
raise Exception(res["retmsg"]) raise Exception(res["message"])
def create_document(self, ds: DataSet, name: str, blob: bytes) -> bool: def create_document(self, ds: DataSet, name: str, blob: bytes) -> bool:
url = f"/doc/dataset/{ds.id}/documents/upload" url = f"/doc/dataset/{ds.id}/documents/upload"

View File

@ -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"

56
sdk/python/test/t_chat.py Normal file
View File

@ -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."

View File

@ -29,7 +29,7 @@ class TestDataset(TestSdk):
else: else:
assert False, f"Failed to create dataset, error: {ds}" 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 Test deleting a dataset with success
""" """
@ -37,7 +37,7 @@ class TestDataset(TestSdk):
ds = rag.create_dataset("MA") ds = rag.create_dataset("MA")
if isinstance(ds, DataSet): if isinstance(ds, DataSet):
assert ds.name == "MA", "Name does not match." 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}" assert res is None, f"Failed to delete dataset, error: {res}"
else: else:
assert False, f"Failed to create dataset, error: {ds}" assert False, f"Failed to create dataset, error: {ds}"