mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 04:37:21 +08:00
### What problem does this PR solve?
This reverts commit 1a608ac411.
### Type of change
- [x] Other (please describe):
This commit is contained in:
253
api/apps/dialog_app.py
Normal file
253
api/apps/dialog_app.py
Normal file
@@ -0,0 +1,253 @@
|
||||
#
|
||||
# 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 quart import request
|
||||
from api.db.services import duplicate_name
|
||||
from api.db.services.dialog_service import DialogService
|
||||
from common.constants import StatusEnum
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.user_service import TenantService, UserTenantService
|
||||
from api.utils.api_utils import get_data_error_result, get_json_result, get_request_json, server_error_response, validate_request
|
||||
from api.utils.tenant_utils import ensure_tenant_model_id_for_params
|
||||
from common.misc_utils import get_uuid
|
||||
from common.constants import RetCode
|
||||
from api.apps import login_required, current_user
|
||||
import logging
|
||||
|
||||
|
||||
@manager.route('/set', methods=['POST']) # noqa: F821
|
||||
@validate_request("prompt_config")
|
||||
@login_required
|
||||
async def set_dialog():
|
||||
req = await get_request_json()
|
||||
dialog_info = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
dialog_id = dialog_info.get("dialog_id", "")
|
||||
is_create = not dialog_id
|
||||
name = dialog_info.get("name", "New Dialog")
|
||||
if not isinstance(name, str):
|
||||
return get_data_error_result(message="Dialog name must be string.")
|
||||
if name.strip() == "":
|
||||
return get_data_error_result(message="Dialog name can't be empty.")
|
||||
if len(name.encode("utf-8")) > 255:
|
||||
return get_data_error_result(message=f"Dialog name length is {len(name)} which is larger than 255")
|
||||
|
||||
name = name.strip()
|
||||
if is_create:
|
||||
# only for chat creating
|
||||
existing_names = {
|
||||
d.name.casefold()
|
||||
for d in DialogService.query(tenant_id=current_user.id, status=StatusEnum.VALID.value)
|
||||
if d.name
|
||||
}
|
||||
if name.casefold() in existing_names:
|
||||
def _name_exists(name: str, **_kwargs) -> bool:
|
||||
return name.casefold() in existing_names
|
||||
|
||||
name = duplicate_name(_name_exists, name=name)
|
||||
|
||||
description = dialog_info.get("description", "A helpful dialog")
|
||||
icon = dialog_info.get("icon", "")
|
||||
top_n = dialog_info.get("top_n", 6)
|
||||
top_k = dialog_info.get("top_k", 1024)
|
||||
rerank_id = dialog_info.get("rerank_id", "")
|
||||
if not rerank_id:
|
||||
dialog_info["rerank_id"] = ""
|
||||
similarity_threshold = dialog_info.get("similarity_threshold", 0.1)
|
||||
vector_similarity_weight = dialog_info.get("vector_similarity_weight", 0.3)
|
||||
llm_setting = dialog_info.get("llm_setting", {})
|
||||
meta_data_filter = dialog_info.get("meta_data_filter", {})
|
||||
prompt_config = dialog_info["prompt_config"]
|
||||
|
||||
# Set default parameters for datasets with knowledge retrieval
|
||||
# All datasets with {knowledge} in system prompt need "knowledge" parameter to enable retrieval
|
||||
kb_ids = dialog_info.get("kb_ids", [])
|
||||
parameters = prompt_config.get("parameters")
|
||||
logging.debug(f"set_dialog: kb_ids={kb_ids}, parameters={parameters}, is_create={not is_create}")
|
||||
# Check if parameters is missing, None, or empty list
|
||||
if kb_ids and not parameters:
|
||||
# Check if system prompt uses {knowledge} placeholder
|
||||
if "{knowledge}" in prompt_config.get("system", ""):
|
||||
# Set default parameters for any dataset with knowledge placeholder
|
||||
prompt_config["parameters"] = [{"key": "knowledge", "optional": False}]
|
||||
logging.debug(f"Set default parameters for datasets with knowledge placeholder: {kb_ids}")
|
||||
|
||||
if not is_create:
|
||||
# only for chat updating
|
||||
if not dialog_info.get("kb_ids", []) and not prompt_config.get("tavily_api_key") and "{knowledge}" in prompt_config.get("system", ""):
|
||||
return get_data_error_result(message="Please remove `{knowledge}` in system prompt since no dataset / Tavily used here.")
|
||||
|
||||
for p in prompt_config.get("parameters", []):
|
||||
if p["optional"]:
|
||||
continue
|
||||
if prompt_config.get("system", "").find("{%s}" % p["key"]) < 0:
|
||||
return get_data_error_result(
|
||||
message="Parameter '{}' is not used".format(p["key"]))
|
||||
|
||||
try:
|
||||
e, tenant = TenantService.get_by_id(current_user.id)
|
||||
if not e:
|
||||
return get_data_error_result(message="Tenant not found!")
|
||||
kbs = KnowledgebaseService.get_by_ids(dialog_info.get("kb_ids", []))
|
||||
embd_ids = [TenantLLMService.split_model_name_and_factory(kb.embd_id)[0] for kb in kbs] # remove vendor suffix for comparison
|
||||
embd_count = len(set(embd_ids))
|
||||
if embd_count > 1:
|
||||
return get_data_error_result(message=f'Datasets use different embedding models: {[kb.embd_id for kb in kbs]}"')
|
||||
|
||||
llm_id = dialog_info.get("llm_id", tenant.llm_id)
|
||||
tenant_llm_id = dialog_info.get("tenant_llm_id", tenant.tenant_llm_id)
|
||||
if not dialog_id:
|
||||
dia = {
|
||||
"id": get_uuid(),
|
||||
"tenant_id": current_user.id,
|
||||
"name": name,
|
||||
"kb_ids": dialog_info.get("kb_ids", []),
|
||||
"description": description,
|
||||
"llm_id": llm_id,
|
||||
"tenant_llm_id": tenant_llm_id,
|
||||
"llm_setting": llm_setting,
|
||||
"prompt_config": prompt_config,
|
||||
"meta_data_filter": meta_data_filter,
|
||||
"top_n": top_n,
|
||||
"top_k": top_k,
|
||||
"rerank_id": rerank_id,
|
||||
"tenant_rerank_id": dialog_info.get("tenant_rerank_id", 0),
|
||||
"similarity_threshold": similarity_threshold,
|
||||
"vector_similarity_weight": vector_similarity_weight,
|
||||
"icon": icon
|
||||
}
|
||||
if not DialogService.save(**dia):
|
||||
return get_data_error_result(message="Fail to new a dialog!")
|
||||
return get_json_result(data=dia)
|
||||
else:
|
||||
del dialog_info["dialog_id"]
|
||||
if "kb_names" in dialog_info:
|
||||
del dialog_info["kb_names"]
|
||||
if not DialogService.update_by_id(dialog_id, dialog_info):
|
||||
return get_data_error_result(message="Dialog not found!")
|
||||
e, dia = DialogService.get_by_id(dialog_id)
|
||||
if not e:
|
||||
return get_data_error_result(message="Fail to update a dialog!")
|
||||
dia = dia.to_dict()
|
||||
dia.update(dialog_info)
|
||||
dia["kb_ids"], dia["kb_names"] = get_kb_names(dia["kb_ids"])
|
||||
return get_json_result(data=dia)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/get', methods=['GET']) # noqa: F821
|
||||
@login_required
|
||||
def get():
|
||||
dialog_id = request.args["dialog_id"]
|
||||
try:
|
||||
e, dia = DialogService.get_by_id(dialog_id)
|
||||
if not e:
|
||||
return get_data_error_result(message="Dialog not found!")
|
||||
dia = dia.to_dict()
|
||||
dia["kb_ids"], dia["kb_names"] = get_kb_names(dia["kb_ids"])
|
||||
return get_json_result(data=dia)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
def get_kb_names(kb_ids):
|
||||
ids, nms = [], []
|
||||
for kid in kb_ids:
|
||||
e, kb = KnowledgebaseService.get_by_id(kid)
|
||||
if not e or kb.status != StatusEnum.VALID.value:
|
||||
continue
|
||||
ids.append(kid)
|
||||
nms.append(kb.name)
|
||||
return ids, nms
|
||||
|
||||
|
||||
@manager.route('/list', methods=['GET']) # noqa: F821
|
||||
@login_required
|
||||
def list_dialogs():
|
||||
try:
|
||||
conversations = DialogService.query(
|
||||
tenant_id=current_user.id,
|
||||
status=StatusEnum.VALID.value,
|
||||
reverse=True,
|
||||
order_by=DialogService.model.create_time)
|
||||
conversations = [d.to_dict() for d in conversations]
|
||||
for conversation in conversations:
|
||||
conversation["kb_ids"], conversation["kb_names"] = get_kb_names(conversation["kb_ids"])
|
||||
return get_json_result(data=conversations)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/next', methods=['POST']) # noqa: F821
|
||||
@login_required
|
||||
async def list_dialogs_next():
|
||||
args = request.args
|
||||
keywords = args.get("keywords", "")
|
||||
page_number = int(args.get("page", 0))
|
||||
items_per_page = int(args.get("page_size", 0))
|
||||
parser_id = args.get("parser_id")
|
||||
orderby = args.get("orderby", "create_time")
|
||||
if args.get("desc", "true").lower() == "false":
|
||||
desc = False
|
||||
else:
|
||||
desc = True
|
||||
|
||||
req = await get_request_json()
|
||||
owner_ids = req.get("owner_ids", [])
|
||||
try:
|
||||
if not owner_ids:
|
||||
# tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
|
||||
# tenants = [tenant["tenant_id"] for tenant in tenants]
|
||||
tenants = [] # keep it here
|
||||
dialogs, total = DialogService.get_by_tenant_ids(
|
||||
tenants, current_user.id, page_number,
|
||||
items_per_page, orderby, desc, keywords, parser_id)
|
||||
else:
|
||||
tenants = owner_ids
|
||||
dialogs, total = DialogService.get_by_tenant_ids(
|
||||
tenants, current_user.id, 0,
|
||||
0, orderby, desc, keywords, parser_id)
|
||||
dialogs = [dialog for dialog in dialogs if dialog["tenant_id"] in tenants]
|
||||
total = len(dialogs)
|
||||
if page_number and items_per_page:
|
||||
dialogs = dialogs[(page_number-1)*items_per_page:page_number*items_per_page]
|
||||
return get_json_result(data={"dialogs": dialogs, "total": total})
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/rm', methods=['POST']) # noqa: F821
|
||||
@login_required
|
||||
@validate_request("dialog_ids")
|
||||
async def rm():
|
||||
req = await get_request_json()
|
||||
dialog_list=[]
|
||||
tenants = UserTenantService.query(user_id=current_user.id)
|
||||
try:
|
||||
for id in req["dialog_ids"]:
|
||||
for tenant in tenants:
|
||||
if DialogService.query(tenant_id=tenant.tenant_id, id=id):
|
||||
break
|
||||
else:
|
||||
return get_json_result(
|
||||
data=False, message='Only owner of dialog authorized for this operation.',
|
||||
code=RetCode.OPERATING_ERROR)
|
||||
dialog_list.append({"id": id,"status":StatusEnum.INVALID.value})
|
||||
DialogService.update_many_by_id(dialog_list)
|
||||
return get_json_result(data=True)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
@@ -1,568 +0,0 @@
|
||||
#
|
||||
# Copyright 2026 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 copy import deepcopy
|
||||
|
||||
from quart import request
|
||||
|
||||
from api.apps import current_user, login_required
|
||||
from api.db.services.dialog_service import DialogService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.services.user_service import TenantService, UserTenantService
|
||||
from api.utils.api_utils import (
|
||||
check_duplicate_ids,
|
||||
get_data_error_result,
|
||||
get_json_result,
|
||||
get_request_json,
|
||||
server_error_response,
|
||||
)
|
||||
from api.utils.tenant_utils import ensure_tenant_model_id_for_params
|
||||
from common.constants import RetCode, StatusEnum
|
||||
from common.misc_utils import get_uuid
|
||||
|
||||
_DEFAULT_PROMPT_CONFIG = {
|
||||
"system": (
|
||||
'You are an intelligent assistant. Please summarize the content of the dataset to answer the question. '
|
||||
'Please list the data in the dataset and answer in detail. When all dataset content is irrelevant to the '
|
||||
'question, your answer must include the sentence "The answer you are looking for is not found in the dataset!" '
|
||||
"Answers need to consider chat history.\n"
|
||||
" Here is the knowledge base:\n"
|
||||
" {knowledge}\n"
|
||||
" The above is the knowledge base."
|
||||
),
|
||||
"prologue": "Hi! I'm your assistant. What can I do for you?",
|
||||
"parameters": [{"key": "knowledge", "optional": False}],
|
||||
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
|
||||
"quote": True,
|
||||
"tts": False,
|
||||
"refine_multiturn": True,
|
||||
}
|
||||
_DEFAULT_RERANK_MODELS = {"BAAI/bge-reranker-v2-m3", "maidalun1020/bce-reranker-base_v1"}
|
||||
_READONLY_FIELDS = {"id", "tenant_id", "created_by", "create_time", "create_date", "update_time", "update_date"}
|
||||
_PERSISTED_FIELDS = set(DialogService.model._meta.fields)
|
||||
|
||||
|
||||
def _build_chat_response(chat):
|
||||
data = chat.to_dict() if hasattr(chat, "to_dict") else dict(chat)
|
||||
kb_ids, kb_names = _resolve_kb_names(data.get("kb_ids", []))
|
||||
data["dataset_ids"] = kb_ids
|
||||
data.pop("kb_ids", None)
|
||||
data["kb_names"] = kb_names
|
||||
return data
|
||||
|
||||
|
||||
def _resolve_kb_names(kb_ids):
|
||||
ids, names = [], []
|
||||
for kb_id in kb_ids or []:
|
||||
ok, kb = KnowledgebaseService.get_by_id(kb_id)
|
||||
if not ok or kb.status != StatusEnum.VALID.value:
|
||||
continue
|
||||
ids.append(kb_id)
|
||||
names.append(kb.name)
|
||||
return ids, names
|
||||
|
||||
|
||||
def _has_knowledge_placeholder(prompt_config):
|
||||
return "{knowledge}" in (prompt_config or {}).get("system", "")
|
||||
|
||||
|
||||
def _validate_name(name, *, required=True):
|
||||
if name is None:
|
||||
if required:
|
||||
return None, "`name` is required."
|
||||
return None, None
|
||||
if not isinstance(name, str):
|
||||
return None, "Chat name must be a string."
|
||||
name = name.strip()
|
||||
if not name:
|
||||
return None, "Chat name can't be empty." if required else "`name` cannot be empty."
|
||||
if len(name.encode("utf-8")) > 255:
|
||||
return None, f"Chat name length is {len(name.encode('utf-8'))} which is larger than 255."
|
||||
return name, None
|
||||
|
||||
|
||||
def _ensure_owned_chat(chat_id):
|
||||
return DialogService.query(
|
||||
tenant_id=current_user.id, id=chat_id, status=StatusEnum.VALID.value
|
||||
)
|
||||
|
||||
|
||||
def _validate_llm_id(llm_id, tenant_id, llm_setting=None):
|
||||
if not llm_id:
|
||||
return None
|
||||
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(llm_id)
|
||||
model_type = (llm_setting or {}).get("model_type")
|
||||
if model_type not in {"chat", "image2text"}:
|
||||
model_type = "chat"
|
||||
|
||||
if not TenantLLMService.query(
|
||||
tenant_id=tenant_id,
|
||||
llm_name=llm_name,
|
||||
llm_factory=llm_factory,
|
||||
model_type=model_type,
|
||||
):
|
||||
return f"`llm_id` {llm_id} doesn't exist"
|
||||
return None
|
||||
|
||||
|
||||
def _validate_rerank_id(rerank_id, tenant_id):
|
||||
if not rerank_id:
|
||||
return None
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(rerank_id)
|
||||
if llm_name in _DEFAULT_RERANK_MODELS:
|
||||
return None
|
||||
if TenantLLMService.query(
|
||||
tenant_id=tenant_id,
|
||||
llm_name=llm_name,
|
||||
llm_factory=llm_factory,
|
||||
model_type="rerank",
|
||||
):
|
||||
return None
|
||||
return f"`rerank_id` {rerank_id} doesn't exist"
|
||||
|
||||
|
||||
def _validate_prompt_config(prompt_config):
|
||||
for parameter in prompt_config.get("parameters", []):
|
||||
if parameter.get("optional"):
|
||||
continue
|
||||
if prompt_config.get("system", "").find("{%s}" % parameter["key"]) < 0:
|
||||
return f"Parameter '{parameter['key']}' is not used"
|
||||
return None
|
||||
|
||||
|
||||
def _validate_dataset_ids(dataset_ids, tenant_id):
|
||||
if dataset_ids is None:
|
||||
return []
|
||||
if not isinstance(dataset_ids, list):
|
||||
return f"`dataset_ids` should be a list."
|
||||
|
||||
normalized_ids = [dataset_id for dataset_id in dataset_ids if dataset_id]
|
||||
kbs = []
|
||||
for dataset_id in normalized_ids:
|
||||
if not KnowledgebaseService.accessible(kb_id=dataset_id, user_id=tenant_id):
|
||||
return f"You don't own the dataset {dataset_id}"
|
||||
matches = KnowledgebaseService.query(id=dataset_id)
|
||||
if not matches:
|
||||
return f"You don't own the dataset {dataset_id}"
|
||||
kb = matches[0]
|
||||
if kb.chunk_num == 0:
|
||||
return f"The dataset {dataset_id} doesn't own parsed file"
|
||||
kbs.append(kb)
|
||||
|
||||
embd_ids = [TenantLLMService.split_model_name_and_factory(kb.embd_id)[0] for kb in kbs]
|
||||
if len(set(embd_ids)) > 1:
|
||||
return f'Datasets use different embedding models: {[kb.embd_id for kb in kbs]}'
|
||||
|
||||
return normalized_ids
|
||||
|
||||
|
||||
def _apply_prompt_defaults(req):
|
||||
prompt_config = req.setdefault("prompt_config", {})
|
||||
for key, value in _DEFAULT_PROMPT_CONFIG.items():
|
||||
temp = prompt_config.get(key)
|
||||
if (key == "system" and not temp) or key not in prompt_config:
|
||||
prompt_config[key] = deepcopy(value)
|
||||
|
||||
if req.get("kb_ids") and not prompt_config.get("parameters") and "{knowledge}" in prompt_config.get("system", ""):
|
||||
prompt_config["parameters"] = [{"key": "knowledge", "optional": False}]
|
||||
|
||||
|
||||
@manager.route("/chats", methods=["POST"]) # noqa: F821
|
||||
@login_required
|
||||
async def create():
|
||||
try:
|
||||
req = await get_request_json()
|
||||
ok, tenant = TenantService.get_by_id(current_user.id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Tenant not found!")
|
||||
|
||||
# Validate tenant_id should not be provided
|
||||
if req.get("tenant_id"):
|
||||
return get_data_error_result(message="`tenant_id` must not be provided.")
|
||||
|
||||
# Validate name
|
||||
name, err = _validate_name(req.get("name"), required=True)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
req["name"] = name
|
||||
|
||||
if "dataset_ids" in req:
|
||||
kb_ids = _validate_dataset_ids(req.get("dataset_ids"), current_user.id)
|
||||
if isinstance(kb_ids, str):
|
||||
return get_data_error_result(message=kb_ids)
|
||||
req["kb_ids"] = kb_ids
|
||||
req.pop("dataset_ids", None)
|
||||
|
||||
if "llm_id" in req:
|
||||
err = _validate_llm_id(req.get("llm_id"), current_user.id, req.get("llm_setting"))
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "rerank_id" in req:
|
||||
err = _validate_rerank_id(req.get("rerank_id"), current_user.id)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "prompt_config" in req:
|
||||
if not isinstance(req["prompt_config"], dict):
|
||||
return get_data_error_result(message="`prompt_config` should be an object.")
|
||||
err = _validate_prompt_config(req["prompt_config"])
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
req.setdefault("kb_ids", [])
|
||||
req.setdefault("llm_id", tenant.llm_id)
|
||||
if req["llm_id"] is None:
|
||||
req["llm_id"] = tenant.llm_id
|
||||
req.setdefault("llm_setting", {})
|
||||
req.setdefault("description", "A helpful Assistant")
|
||||
req.setdefault("top_n", 6)
|
||||
req.setdefault("top_k", 1024)
|
||||
req.setdefault("rerank_id", "")
|
||||
req.setdefault("similarity_threshold", 0.1)
|
||||
req.setdefault("vector_similarity_weight", 0.3)
|
||||
req.setdefault("icon", "")
|
||||
_apply_prompt_defaults(req)
|
||||
err = _validate_prompt_config(req["prompt_config"])
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
req = {field: value for field, value in req.items() if field in _PERSISTED_FIELDS}
|
||||
for field in _READONLY_FIELDS:
|
||||
req.pop(field, None)
|
||||
|
||||
if DialogService.query(
|
||||
name=req["name"],
|
||||
tenant_id=current_user.id,
|
||||
status=StatusEnum.VALID.value,
|
||||
):
|
||||
return get_data_error_result(message="Duplicated chat name in creating chat.")
|
||||
|
||||
req["id"] = get_uuid()
|
||||
req["tenant_id"] = current_user.id
|
||||
if not DialogService.save(**req):
|
||||
return get_data_error_result(message="Failed to create chat.")
|
||||
|
||||
ok, chat = DialogService.get_by_id(req["id"])
|
||||
if not ok:
|
||||
return get_data_error_result(message="Failed to retrieve created chat.")
|
||||
return get_json_result(data=_build_chat_response(chat))
|
||||
except Exception as ex:
|
||||
return server_error_response(ex)
|
||||
|
||||
|
||||
@manager.route("/chats", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
def list_chats():
|
||||
chat_id = request.args.get("id")
|
||||
name = request.args.get("name")
|
||||
keywords = request.args.get("keywords", "")
|
||||
orderby = request.args.get("orderby", "create_time")
|
||||
desc = request.args.get("desc", "true").lower() != "false"
|
||||
owner_ids = request.args.getlist("owner_ids")
|
||||
exact_filters = {"id": chat_id, "name": name}
|
||||
if chat_id or name:
|
||||
keywords = ""
|
||||
|
||||
try:
|
||||
page_number = int(request.args.get("page", 1))
|
||||
items_per_page = int(request.args.get("page_size", 0))
|
||||
if owner_ids:
|
||||
chats, total = DialogService.get_by_tenant_ids(
|
||||
owner_ids, current_user.id, 0, 0, orderby, desc, keywords, **exact_filters
|
||||
)
|
||||
chats = [chat for chat in chats if chat["tenant_id"] in owner_ids]
|
||||
total = len(chats)
|
||||
if page_number and items_per_page:
|
||||
start = (page_number - 1) * items_per_page
|
||||
chats = chats[start : start + items_per_page]
|
||||
else:
|
||||
chats, total = DialogService.get_by_tenant_ids(
|
||||
[], current_user.id, page_number, items_per_page, orderby, desc, keywords, **exact_filters
|
||||
)
|
||||
|
||||
return get_json_result(
|
||||
data={"chats": [_build_chat_response(chat) for chat in chats], "total": total}
|
||||
)
|
||||
except Exception as ex:
|
||||
return server_error_response(ex)
|
||||
|
||||
|
||||
@manager.route("/chats/<chat_id>", methods=["GET"]) # noqa: F821
|
||||
@login_required
|
||||
def get_chat(chat_id):
|
||||
try:
|
||||
tenants = UserTenantService.query(user_id=current_user.id)
|
||||
for tenant in tenants:
|
||||
if DialogService.query(
|
||||
tenant_id=tenant.tenant_id, id=chat_id, status=StatusEnum.VALID.value
|
||||
):
|
||||
break
|
||||
else:
|
||||
return get_json_result(
|
||||
data=False,
|
||||
message="No authorization.",
|
||||
code=RetCode.AUTHENTICATION_ERROR,
|
||||
)
|
||||
|
||||
ok, chat = DialogService.get_by_id(chat_id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Chat not found!")
|
||||
return get_json_result(data=_build_chat_response(chat))
|
||||
except Exception as ex:
|
||||
return server_error_response(ex)
|
||||
|
||||
|
||||
@manager.route("/chats/<chat_id>", methods=["PUT"]) # noqa: F821
|
||||
@login_required
|
||||
async def update_chat(chat_id):
|
||||
if not _ensure_owned_chat(chat_id):
|
||||
return get_json_result(
|
||||
data=False, message="No authorization.", code=RetCode.AUTHENTICATION_ERROR
|
||||
)
|
||||
|
||||
try:
|
||||
req = await get_request_json()
|
||||
ok, tenant = TenantService.get_by_id(current_user.id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Tenant not found!")
|
||||
|
||||
ok, current_chat = DialogService.get_by_id(chat_id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Chat not found!")
|
||||
current_chat = current_chat.to_dict()
|
||||
|
||||
if req.get("tenant_id"):
|
||||
return get_data_error_result(message="`tenant_id` must not be provided.")
|
||||
|
||||
if "name" in req:
|
||||
name, err = _validate_name(req.get("name"), required=True)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
req["name"] = name
|
||||
|
||||
if "dataset_ids" in req:
|
||||
kb_ids = _validate_dataset_ids(req.get("dataset_ids"), current_user.id)
|
||||
if isinstance(kb_ids, str):
|
||||
return get_data_error_result(message=kb_ids)
|
||||
req["kb_ids"] = kb_ids
|
||||
req.pop("dataset_ids", None)
|
||||
|
||||
if "llm_id" in req:
|
||||
err = _validate_llm_id(req.get("llm_id"), current_user.id, req.get("llm_setting"))
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "rerank_id" in req:
|
||||
err = _validate_rerank_id(req.get("rerank_id"), current_user.id)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "prompt_config" in req:
|
||||
if not isinstance(req["prompt_config"], dict):
|
||||
return get_data_error_result(message="`prompt_config` should be an object.")
|
||||
err = _validate_prompt_config(req["prompt_config"])
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
prompt_config = req.get("prompt_config", {})
|
||||
if not prompt_config:
|
||||
prompt_config = current_chat.get("prompt_config", {})
|
||||
kb_ids = req.get("kb_ids", current_chat.get("kb_ids", []))
|
||||
if not kb_ids and not prompt_config.get("tavily_api_key") and _has_knowledge_placeholder(prompt_config):
|
||||
return get_data_error_result(message="Please remove `{knowledge}` in system prompt since no dataset / Tavily used here.")
|
||||
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
req = {field: value for field, value in req.items() if field in _PERSISTED_FIELDS}
|
||||
for field in _READONLY_FIELDS:
|
||||
req.pop(field, None)
|
||||
|
||||
if (
|
||||
"name" in req
|
||||
and req["name"].lower() != current_chat["name"].lower()
|
||||
and DialogService.query(
|
||||
name=req["name"],
|
||||
tenant_id=current_user.id,
|
||||
status=StatusEnum.VALID.value,
|
||||
)
|
||||
):
|
||||
return get_data_error_result(message="Duplicated chat name.")
|
||||
|
||||
if not DialogService.update_by_id(chat_id, req):
|
||||
return get_data_error_result(message="Chat not found!")
|
||||
|
||||
ok, chat = DialogService.get_by_id(chat_id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Failed to retrieve updated chat.")
|
||||
return get_json_result(data=_build_chat_response(chat))
|
||||
except Exception as ex:
|
||||
return server_error_response(ex)
|
||||
|
||||
|
||||
@manager.route("/chats/<chat_id>", methods=["PATCH"]) # noqa: F821
|
||||
@login_required
|
||||
async def patch_chat(chat_id):
|
||||
if not _ensure_owned_chat(chat_id):
|
||||
return get_json_result(
|
||||
data=False, message="No authorization.", code=RetCode.AUTHENTICATION_ERROR
|
||||
)
|
||||
|
||||
try:
|
||||
req = await get_request_json()
|
||||
ok, tenant = TenantService.get_by_id(current_user.id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Tenant not found!")
|
||||
|
||||
ok, current_chat = DialogService.get_by_id(chat_id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Chat not found!")
|
||||
current_chat = current_chat.to_dict()
|
||||
|
||||
if req.get("tenant_id"):
|
||||
return get_data_error_result(message="`tenant_id` must not be provided.")
|
||||
|
||||
if "name" in req:
|
||||
name, err = _validate_name(req.get("name"), required=False)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
if name is not None:
|
||||
req["name"] = name
|
||||
|
||||
if "dataset_ids" in req:
|
||||
kb_ids = _validate_dataset_ids(req.get("dataset_ids"), current_user.id)
|
||||
if isinstance(kb_ids, str):
|
||||
return get_data_error_result(message=kb_ids)
|
||||
req["kb_ids"] = kb_ids
|
||||
req.pop("dataset_ids", None)
|
||||
|
||||
if "llm_id" in req:
|
||||
err = _validate_llm_id(req.get("llm_id"), current_user.id, req.get("llm_setting"))
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "rerank_id" in req:
|
||||
err = _validate_rerank_id(req.get("rerank_id"), current_user.id)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "prompt_config" in req:
|
||||
if not isinstance(req["prompt_config"], dict):
|
||||
return get_data_error_result(message="`prompt_config` should be an object.")
|
||||
prompt_config = deepcopy(current_chat.get("prompt_config", {}))
|
||||
prompt_config.update(req["prompt_config"])
|
||||
req["prompt_config"] = prompt_config
|
||||
err = _validate_prompt_config(prompt_config)
|
||||
if err:
|
||||
return get_data_error_result(message=err)
|
||||
|
||||
if "llm_setting" in req:
|
||||
llm_setting = deepcopy(current_chat.get("llm_setting", {}))
|
||||
llm_setting.update(req["llm_setting"])
|
||||
req["llm_setting"] = llm_setting
|
||||
|
||||
if "prompt_config" in req or "kb_ids" in req:
|
||||
prompt_config = req.get("prompt_config", current_chat.get("prompt_config", {}))
|
||||
kb_ids = req.get("kb_ids", current_chat.get("kb_ids", []))
|
||||
if not kb_ids and not prompt_config.get("tavily_api_key") and _has_knowledge_placeholder(prompt_config):
|
||||
return get_data_error_result(message="Please remove `{knowledge}` in system prompt since no dataset / Tavily used here.")
|
||||
|
||||
req = ensure_tenant_model_id_for_params(current_user.id, req)
|
||||
req = {field: value for field, value in req.items() if field in _PERSISTED_FIELDS}
|
||||
for field in _READONLY_FIELDS:
|
||||
req.pop(field, None)
|
||||
|
||||
if (
|
||||
"name" in req
|
||||
and req["name"].lower() != current_chat["name"].lower()
|
||||
and DialogService.query(
|
||||
name=req["name"],
|
||||
tenant_id=current_user.id,
|
||||
status=StatusEnum.VALID.value,
|
||||
)
|
||||
):
|
||||
return get_data_error_result(message="Duplicated chat name.")
|
||||
|
||||
if not DialogService.update_by_id(chat_id, req):
|
||||
return get_data_error_result(message="Failed to update chat.")
|
||||
|
||||
ok, chat = DialogService.get_by_id(chat_id)
|
||||
if not ok:
|
||||
return get_data_error_result(message="Failed to retrieve updated chat.")
|
||||
return get_json_result(data=_build_chat_response(chat))
|
||||
except Exception as ex:
|
||||
return server_error_response(ex)
|
||||
|
||||
|
||||
@manager.route("/chats/<chat_id>", methods=["DELETE"]) # noqa: F821
|
||||
@login_required
|
||||
def delete_chat(chat_id):
|
||||
if not _ensure_owned_chat(chat_id):
|
||||
return get_json_result(
|
||||
data=False, message="No authorization.", code=RetCode.AUTHENTICATION_ERROR
|
||||
)
|
||||
|
||||
try:
|
||||
if not DialogService.update_by_id(chat_id, {"status": StatusEnum.INVALID.value}):
|
||||
return get_data_error_result(message=f"Failed to delete chat {chat_id}")
|
||||
return get_json_result(data=True)
|
||||
except Exception as ex:
|
||||
return server_error_response(ex)
|
||||
|
||||
|
||||
@manager.route("/chats", methods=["DELETE"]) # noqa: F821
|
||||
@login_required
|
||||
async def bulk_delete_chats():
|
||||
req = await get_request_json()
|
||||
if not req:
|
||||
return get_json_result(data={})
|
||||
|
||||
ids = req.get("ids")
|
||||
if not ids:
|
||||
if req.get("delete_all") is True:
|
||||
ids = [
|
||||
chat.id
|
||||
for chat in DialogService.query(
|
||||
tenant_id=current_user.id, status=StatusEnum.VALID.value
|
||||
)
|
||||
]
|
||||
if not ids:
|
||||
return get_json_result(data={})
|
||||
else:
|
||||
return get_json_result(data={})
|
||||
|
||||
errors = []
|
||||
success_count = 0
|
||||
unique_ids, duplicate_messages = check_duplicate_ids(ids, "chat")
|
||||
|
||||
for chat_id in unique_ids:
|
||||
if not _ensure_owned_chat(chat_id):
|
||||
errors.append(f"Chat({chat_id}) not found.")
|
||||
continue
|
||||
success_count += DialogService.update_by_id(chat_id, {"status": StatusEnum.INVALID.value})
|
||||
|
||||
all_errors = errors + duplicate_messages
|
||||
if all_errors:
|
||||
if success_count > 0:
|
||||
return get_json_result(
|
||||
data={"success_count": success_count, "errors": all_errors},
|
||||
message=f"Partially deleted {success_count} chats with {len(all_errors)} errors",
|
||||
)
|
||||
return get_data_error_result(message="; ".join(all_errors))
|
||||
|
||||
return get_json_result(data={"success_count": success_count})
|
||||
329
api/apps/sdk/chat.py
Normal file
329
api/apps/sdk/chat.py
Normal file
@@ -0,0 +1,329 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
import logging
|
||||
from quart import request
|
||||
from api.db.services.dialog_service import DialogService
|
||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||
from api.db.services.tenant_llm_service import TenantLLMService
|
||||
from api.db.services.user_service import TenantService
|
||||
from common.misc_utils import get_uuid
|
||||
from common.constants import RetCode, StatusEnum
|
||||
from api.utils.api_utils import check_duplicate_ids, get_error_data_result, get_result, token_required, get_request_json
|
||||
|
||||
|
||||
@manager.route("/chats", methods=["POST"]) # noqa: F821
|
||||
@token_required
|
||||
async def create(tenant_id):
|
||||
req = await get_request_json()
|
||||
ids = [i for i in req.get("dataset_ids", []) if i]
|
||||
for kb_id in ids:
|
||||
kbs = KnowledgebaseService.accessible(kb_id=kb_id, user_id=tenant_id)
|
||||
if not kbs:
|
||||
return get_error_data_result(f"You don't own the dataset {kb_id}")
|
||||
kbs = KnowledgebaseService.query(id=kb_id)
|
||||
kb = kbs[0]
|
||||
if kb.chunk_num == 0:
|
||||
return get_error_data_result(f"The dataset {kb_id} doesn't own parsed file")
|
||||
|
||||
kbs = KnowledgebaseService.get_by_ids(ids) if ids else []
|
||||
embd_ids = [TenantLLMService.split_model_name_and_factory(kb.embd_id)[0] for kb in kbs] # remove vendor suffix for comparison
|
||||
embd_count = list(set(embd_ids))
|
||||
if len(embd_count) > 1:
|
||||
return get_result(message='Datasets use different embedding models."', code=RetCode.AUTHENTICATION_ERROR)
|
||||
req["kb_ids"] = ids
|
||||
# llm
|
||||
llm = req.get("llm")
|
||||
if llm:
|
||||
if "model_name" in llm:
|
||||
req["llm_id"] = llm.pop("model_name")
|
||||
if req.get("llm_id") is not None:
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(req["llm_id"])
|
||||
model_type = llm.get("model_type")
|
||||
model_type = model_type if model_type in ["chat", "image2text"] else "chat"
|
||||
if not TenantLLMService.query(tenant_id=tenant_id, llm_name=llm_name, llm_factory=llm_factory, model_type=model_type):
|
||||
return get_error_data_result(f"`model_name` {req.get('llm_id')} doesn't exist")
|
||||
req["llm_setting"] = req.pop("llm")
|
||||
e, tenant = TenantService.get_by_id(tenant_id)
|
||||
if not e:
|
||||
return get_error_data_result(message="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", "top_k"]
|
||||
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("rerank_id"):
|
||||
value_rerank_model = ["BAAI/bge-reranker-v2-m3", "maidalun1020/bce-reranker-base_v1"]
|
||||
if req["rerank_id"] not in value_rerank_model and not TenantLLMService.query(tenant_id=tenant_id, llm_name=req.get("rerank_id"), model_type="rerank"):
|
||||
return get_error_data_result(f"`rerank_model` {req.get('rerank_id')} doesn't exist")
|
||||
if not req.get("llm_id"):
|
||||
req["llm_id"] = tenant.llm_id
|
||||
if not req.get("name"):
|
||||
return get_error_data_result(message="`name` is required.")
|
||||
if DialogService.query(name=req["name"], tenant_id=tenant_id, status=StatusEnum.VALID.value):
|
||||
return get_error_data_result(message="Duplicated chat name in creating chat.")
|
||||
# tenant_id
|
||||
if req.get("tenant_id"):
|
||||
return get_error_data_result(message="`tenant_id` must not be provided.")
|
||||
req["tenant_id"] = tenant_id
|
||||
# prompt more parameter
|
||||
default_prompt = {
|
||||
"system": """You are an intelligent assistant. Please summarize the content of the dataset to answer the question. Please list the data in the dataset and answer in detail. When all dataset content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the dataset!" Answers need to consider chat history.
|
||||
Here is the knowledge base:
|
||||
{knowledge}
|
||||
The above is the knowledge base.""",
|
||||
"prologue": "Hi! I'm your assistant. What can I do for you?",
|
||||
"parameters": [{"key": "knowledge", "optional": False}],
|
||||
"empty_response": "Sorry! No relevant content was found in the knowledge base!",
|
||||
"quote": True,
|
||||
"tts": False,
|
||||
"refine_multiturn": True,
|
||||
}
|
||||
key_list_2 = ["system", "prologue", "parameters", "empty_response", "quote", "tts", "refine_multiturn"]
|
||||
if "prompt_config" not in req:
|
||||
req["prompt_config"] = {}
|
||||
for key in key_list_2:
|
||||
temp = req["prompt_config"].get(key)
|
||||
if (not temp and key == "system") or (key not in req["prompt_config"]):
|
||||
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(message="Parameter '{}' is not used".format(p["key"]))
|
||||
# save
|
||||
if not DialogService.save(**req):
|
||||
return get_error_data_result(message="Fail to new a chat!")
|
||||
# response
|
||||
e, res = DialogService.get_by_id(req["id"])
|
||||
if not e:
|
||||
return get_error_data_result(message="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": 1 - 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["dataset_ids"] = req.get("dataset_ids", [])
|
||||
res["avatar"] = res.pop("icon")
|
||||
return get_result(data=res)
|
||||
|
||||
|
||||
@manager.route("/chats/<chat_id>", methods=["PUT"]) # noqa: F821
|
||||
@token_required
|
||||
async 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(message="You do not own the chat")
|
||||
req = await get_request_json()
|
||||
ids = req.get("dataset_ids", [])
|
||||
if "show_quotation" in req:
|
||||
req["do_refer"] = req.pop("show_quotation")
|
||||
if ids:
|
||||
for kb_id in ids:
|
||||
kbs = KnowledgebaseService.accessible(kb_id=kb_id, user_id=tenant_id)
|
||||
if not kbs:
|
||||
return get_error_data_result(f"You don't own the dataset {kb_id}")
|
||||
kbs = KnowledgebaseService.query(id=kb_id)
|
||||
kb = kbs[0]
|
||||
if kb.chunk_num == 0:
|
||||
return get_error_data_result(f"The dataset {kb_id} doesn't own parsed file")
|
||||
|
||||
kbs = KnowledgebaseService.get_by_ids(ids)
|
||||
embd_ids = [TenantLLMService.split_model_name_and_factory(kb.embd_id)[0] for kb in kbs] # remove vendor suffix for comparison
|
||||
embd_count = list(set(embd_ids))
|
||||
if len(embd_count) > 1:
|
||||
return get_result(message='Datasets use different embedding models."', code=RetCode.AUTHENTICATION_ERROR)
|
||||
req["kb_ids"] = ids
|
||||
else:
|
||||
req["kb_ids"] = []
|
||||
llm = req.get("llm")
|
||||
if llm:
|
||||
if "model_name" in llm:
|
||||
req["llm_id"] = llm.pop("model_name")
|
||||
if req.get("llm_id") is not None:
|
||||
llm_name, llm_factory = TenantLLMService.split_model_name_and_factory(req["llm_id"])
|
||||
model_type = llm.get("model_type")
|
||||
model_type = model_type if model_type in ["chat", "image2text"] else "chat"
|
||||
if not TenantLLMService.query(tenant_id=tenant_id, llm_name=llm_name, llm_factory=llm_factory, model_type=model_type):
|
||||
return get_error_data_result(f"`model_name` {req.get('llm_id')} doesn't exist")
|
||||
req["llm_setting"] = req.pop("llm")
|
||||
e, tenant = TenantService.get_by_id(tenant_id)
|
||||
if not e:
|
||||
return get_error_data_result(message="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", "top_k"]
|
||||
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 req.get("rerank_id"):
|
||||
value_rerank_model = ["BAAI/bge-reranker-v2-m3", "maidalun1020/bce-reranker-base_v1"]
|
||||
if req["rerank_id"] not in value_rerank_model and not TenantLLMService.query(tenant_id=tenant_id, llm_name=req.get("rerank_id"), model_type="rerank"):
|
||||
return get_error_data_result(f"`rerank_model` {req.get('rerank_id')} doesn't exist")
|
||||
if "name" in req:
|
||||
if not req.get("name"):
|
||||
return get_error_data_result(message="`name` cannot be 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(message="Duplicated chat name in updating chat.")
|
||||
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(message="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 "dataset_ids" in req:
|
||||
req.pop("dataset_ids")
|
||||
if not DialogService.update_by_id(chat_id, req):
|
||||
return get_error_data_result(message="Chat not found!")
|
||||
return get_result()
|
||||
|
||||
|
||||
@manager.route("/chats", methods=["DELETE"]) # noqa: F821
|
||||
@token_required
|
||||
async def delete_chats(tenant_id):
|
||||
errors = []
|
||||
success_count = 0
|
||||
req = await get_request_json()
|
||||
if not req:
|
||||
return get_result()
|
||||
|
||||
ids = req.get("ids")
|
||||
if not ids:
|
||||
if req.get("delete_all") is True:
|
||||
ids = [d.id for d in DialogService.query(tenant_id=tenant_id, status=StatusEnum.VALID.value)]
|
||||
if not ids:
|
||||
return get_result()
|
||||
else:
|
||||
return get_result()
|
||||
|
||||
id_list = ids
|
||||
|
||||
unique_id_list, duplicate_messages = check_duplicate_ids(id_list, "assistant")
|
||||
|
||||
for id in unique_id_list:
|
||||
if not DialogService.query(tenant_id=tenant_id, id=id, status=StatusEnum.VALID.value):
|
||||
errors.append(f"Assistant({id}) not found.")
|
||||
continue
|
||||
temp_dict = {"status": StatusEnum.INVALID.value}
|
||||
success_count += DialogService.update_by_id(id, temp_dict)
|
||||
|
||||
if errors:
|
||||
if success_count > 0:
|
||||
return get_result(data={"success_count": success_count, "errors": errors}, message=f"Partially deleted {success_count} chats with {len(errors)} errors")
|
||||
else:
|
||||
return get_error_data_result(message="; ".join(errors))
|
||||
|
||||
if duplicate_messages:
|
||||
if success_count > 0:
|
||||
return get_result(message=f"Partially deleted {success_count} chats with {len(duplicate_messages)} errors", data={"success_count": success_count, "errors": duplicate_messages})
|
||||
else:
|
||||
return get_error_data_result(message=";".join(duplicate_messages))
|
||||
|
||||
return get_result()
|
||||
|
||||
|
||||
@manager.route("/chats", methods=["GET"]) # noqa: F821
|
||||
@token_required
|
||||
def list_chat(tenant_id):
|
||||
id = request.args.get("id")
|
||||
name = request.args.get("name")
|
||||
if id or name:
|
||||
chat = DialogService.query(id=id, name=name, status=StatusEnum.VALID.value, tenant_id=tenant_id)
|
||||
if not chat:
|
||||
return get_error_data_result(message="The chat doesn't exist")
|
||||
page_number = int(request.args.get("page", 1))
|
||||
items_per_page = int(request.args.get("page_size", 30))
|
||||
orderby = request.args.get("orderby", "create_time")
|
||||
if request.args.get("desc") == "False" or 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_assistants = []
|
||||
key_mapping = {
|
||||
"parameters": "variables",
|
||||
"prologue": "opener",
|
||||
"quote": "show_quote",
|
||||
"system": "prompt",
|
||||
"rerank_id": "rerank_model",
|
||||
"vector_similarity_weight": "keywords_similarity_weight",
|
||||
"do_refer": "show_quotation",
|
||||
}
|
||||
key_list = ["similarity_threshold", "vector_similarity_weight", "top_n", "rerank_id"]
|
||||
for res in chats:
|
||||
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": 1 - 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:
|
||||
logging.warning(f"The kb {kb_id} does not exist.")
|
||||
continue
|
||||
kb_list.append(kb[0].to_json())
|
||||
del res["kb_ids"]
|
||||
res["datasets"] = kb_list
|
||||
res["avatar"] = res.pop("icon")
|
||||
list_assistants.append(res)
|
||||
return get_result(data=list_assistants)
|
||||
@@ -105,18 +105,7 @@ class DialogService(CommonService):
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_by_tenant_ids(
|
||||
cls,
|
||||
joined_tenant_ids,
|
||||
user_id,
|
||||
page_number,
|
||||
items_per_page,
|
||||
orderby,
|
||||
desc,
|
||||
keywords,
|
||||
id=None,
|
||||
name=None,
|
||||
):
|
||||
def get_by_tenant_ids(cls, joined_tenant_ids, user_id, page_number, items_per_page, orderby, desc, keywords, parser_id=None):
|
||||
from api.db.db_models import User
|
||||
|
||||
fields = [
|
||||
@@ -143,20 +132,25 @@ class DialogService(CommonService):
|
||||
cls.model.update_time,
|
||||
cls.model.create_time,
|
||||
]
|
||||
dialogs = (
|
||||
cls.model.select(*fields)
|
||||
.join(User, on=(cls.model.tenant_id == User.id))
|
||||
.where(
|
||||
(cls.model.tenant_id.in_(joined_tenant_ids) | (cls.model.tenant_id == user_id))
|
||||
& (cls.model.status == StatusEnum.VALID.value),
|
||||
)
|
||||
)
|
||||
if id:
|
||||
dialogs = dialogs.where(cls.model.id == id)
|
||||
if name:
|
||||
dialogs = dialogs.where(cls.model.name == name)
|
||||
if keywords:
|
||||
dialogs = dialogs.where(fn.LOWER(cls.model.name).contains(keywords.lower()))
|
||||
dialogs = (
|
||||
cls.model.select(*fields)
|
||||
.join(User, on=(cls.model.tenant_id == User.id))
|
||||
.where(
|
||||
(cls.model.tenant_id.in_(joined_tenant_ids) | (cls.model.tenant_id == user_id)) & (cls.model.status == StatusEnum.VALID.value),
|
||||
(fn.LOWER(cls.model.name).contains(keywords.lower())),
|
||||
)
|
||||
)
|
||||
else:
|
||||
dialogs = (
|
||||
cls.model.select(*fields)
|
||||
.join(User, on=(cls.model.tenant_id == User.id))
|
||||
.where(
|
||||
(cls.model.tenant_id.in_(joined_tenant_ids) | (cls.model.tenant_id == user_id)) & (cls.model.status == StatusEnum.VALID.value),
|
||||
)
|
||||
)
|
||||
if parser_id:
|
||||
dialogs = dialogs.where(cls.model.parser_id == parser_id)
|
||||
if desc:
|
||||
dialogs = dialogs.order_by(cls.model.getter_by(orderby).desc())
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user