Refactor API for document and session (#2819)

### What problem does this PR solve?

Refactor API for document and session.

### Type of change


- [x] Refactoring

---------

Co-authored-by: liuhua <10215101452@stu.ecun.edu.cn>
This commit is contained in:
liuhua 2024-10-12 19:35:19 +08:00 committed by GitHub
parent 7d80fc474c
commit 6eed115723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 821 additions and 745 deletions

View File

@ -4,9 +4,11 @@ import datetime
import json import json
import traceback import traceback
from botocore.docs.method import document_model_driven_method
from flask import request from flask import request
from flask_login import login_required, current_user from flask_login import login_required, current_user
from elasticsearch_dsl import Q from elasticsearch_dsl import Q
from sphinx.addnodes import document
from rag.app.qa import rmPrefix, beAdoc from rag.app.qa import rmPrefix, beAdoc
from rag.nlp import search, rag_tokenizer, keyword_extraction from rag.nlp import search, rag_tokenizer, keyword_extraction
@ -16,22 +18,22 @@ from api.db import LLMType, ParserType
from api.db.services.knowledgebase_service import KnowledgebaseService from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import TenantLLMService from api.db.services.llm_service import TenantLLMService
from api.db.services.user_service import UserTenantService from api.db.services.user_service import UserTenantService
from api.utils.api_utils import server_error_response, get_data_error_result, validate_request from api.utils.api_utils import server_error_response, get_error_data_result, validate_request
from api.db.services.document_service import DocumentService from api.db.services.document_service import DocumentService
from api.settings import RetCode, retrievaler, kg_retrievaler from api.settings import RetCode, retrievaler, kg_retrievaler
from api.utils.api_utils import get_json_result from api.utils.api_utils import get_result
import hashlib import hashlib
import re import re
from api.utils.api_utils import get_json_result, token_required, get_data_error_result from api.utils.api_utils import get_result, token_required, get_error_data_result
from api.db.db_models import Task, File from api.db.db_models import Task, File
from api.db.services.task_service import TaskService, queue_tasks from api.db.services.task_service import TaskService, queue_tasks
from api.db.services.user_service import TenantService, UserTenantService from api.db.services.user_service import TenantService, UserTenantService
from api.utils.api_utils import server_error_response, get_data_error_result, validate_request from api.utils.api_utils import server_error_response, get_error_data_result, validate_request
from api.utils.api_utils import get_json_result from api.utils.api_utils import get_result, get_result, get_error_data_result
from functools import partial from functools import partial
from io import BytesIO from io import BytesIO
@ -59,307 +61,163 @@ MAXIMUM_OF_UPLOADING_FILES = 256
MAXIMUM_OF_UPLOADING_FILES = 256 MAXIMUM_OF_UPLOADING_FILES = 256
@manager.route('/dataset/<dataset_id>/documents/upload', methods=['POST']) @manager.route('/dataset/<dataset_id>/document', methods=['POST'])
@token_required @token_required
def upload(dataset_id, tenant_id): def upload(dataset_id, tenant_id):
if 'file' not in request.files: if 'file' not in request.files:
return get_json_result( return get_error_data_result(
data=False, retmsg='No file part!', retcode=RetCode.ARGUMENT_ERROR) retmsg='No file part!', retcode=RetCode.ARGUMENT_ERROR)
file_objs = request.files.getlist('file') file_objs = request.files.getlist('file')
for file_obj in file_objs: for file_obj in file_objs:
if file_obj.filename == '': if file_obj.filename == '':
return get_json_result( return get_result(
data=False, retmsg='No file selected!', retcode=RetCode.ARGUMENT_ERROR) retmsg='No file selected!', retcode=RetCode.ARGUMENT_ERROR)
e, kb = KnowledgebaseService.get_by_id(dataset_id) e, kb = KnowledgebaseService.get_by_id(dataset_id)
if not e: if not e:
raise LookupError(f"Can't find the knowledgebase with ID {dataset_id}!") raise LookupError(f"Can't find the knowledgebase with ID {dataset_id}!")
err, _ = FileService.upload_document(kb, file_objs, tenant_id) err, _ = FileService.upload_document(kb, file_objs, tenant_id)
if err: if err:
return get_json_result( return get_result(
data=False, retmsg="\n".join(err), retcode=RetCode.SERVER_ERROR) retmsg="\n".join(err), retcode=RetCode.SERVER_ERROR)
return get_json_result(data=True) return get_result()
@manager.route('/infos', methods=['GET']) @manager.route('/dataset/<dataset_id>/info/<document_id>', methods=['PUT'])
@token_required @token_required
def docinfos(tenant_id): def update_doc(tenant_id, dataset_id, document_id):
req = request.args
if "id" not in req and "name" not in req:
return get_data_error_result(
retmsg="Id or name should be provided")
doc_id=None
if "id" in req:
doc_id = req["id"]
if "name" in req:
doc_name = req["name"]
doc_id = DocumentService.get_doc_id_by_doc_name(doc_name)
e, doc = DocumentService.get_by_id(doc_id)
#rename key's name
key_mapping = {
"chunk_num": "chunk_count",
"kb_id": "knowledgebase_id",
"token_num": "token_count",
"parser_id":"parser_method",
}
renamed_doc = {}
for key, value in doc.to_dict().items():
new_key = key_mapping.get(key, key)
renamed_doc[new_key] = value
return get_json_result(data=renamed_doc)
@manager.route('/save', methods=['POST'])
@token_required
def save_doc(tenant_id):
req = request.json req = request.json
#get doc by id or name if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
doc_id = None return get_error_data_result(retmsg='You do not own the dataset.')
if "id" in req: doc = DocumentService.query(kb_id=dataset_id, id=document_id)
doc_id = req["id"] if not doc:
elif "name" in req: return get_error_data_result(retmsg='The dataset not own the document.')
doc_name = req["name"] doc = doc[0]
doc_id = DocumentService.get_doc_id_by_doc_name(doc_name)
if not doc_id:
return get_json_result(retcode=400, retmsg="Document ID or name is required")
e, doc = DocumentService.get_by_id(doc_id)
if not e:
return get_data_error_result(retmsg="Document not found!")
#other value can't be changed
if "chunk_count" in req: if "chunk_count" in req:
if req["chunk_count"] != doc.chunk_num: if req["chunk_count"] != doc.chunk_num:
return get_data_error_result( return get_error_data_result(retmsg="Can't change chunk_count.")
retmsg="Can't change chunk_count.")
if "token_count" in req: if "token_count" in req:
if req["token_count"] != doc.token_num: if req["token_count"] != doc.token_num:
return get_data_error_result( return get_error_data_result(retmsg="Can't change token_count.")
retmsg="Can't change token_count.")
if "progress" in req: if "progress" in req:
if req['progress'] != doc.progress: if req['progress'] != doc.progress:
return get_data_error_result( return get_error_data_result(retmsg="Can't change progress.")
retmsg="Can't change progress.")
#change name or parse_method
if "name" in req and req["name"] != doc.name: if "name" in req and req["name"] != doc.name:
try: if pathlib.Path(req["name"].lower()).suffix != pathlib.Path(doc.name.lower()).suffix:
if pathlib.Path(req["name"].lower()).suffix != pathlib.Path( return get_result(retmsg="The extension of file can't be changed", retcode=RetCode.ARGUMENT_ERROR)
doc.name.lower()).suffix: for d in DocumentService.query(name=req["name"], kb_id=doc.kb_id):
return get_json_result( if d.name == req["name"]:
data=False, return get_error_data_result(
retmsg="The extension of file can't be changed", retmsg="Duplicated document name in the same knowledgebase.")
retcode=RetCode.ARGUMENT_ERROR) if not DocumentService.update_by_id(
for d in DocumentService.query(name=req["name"], kb_id=doc.kb_id): document_id, {"name": req["name"]}):
if d.name == req["name"]: return get_error_data_result(
return get_data_error_result( retmsg="Database error (Document rename)!")
retmsg="Duplicated document name in the same knowledgebase.")
if not DocumentService.update_by_id( informs = File2DocumentService.get_by_document_id(document_id)
doc_id, {"name": req["name"]}): if informs:
return get_data_error_result( e, file = FileService.get_by_id(informs[0].file_id)
retmsg="Database error (Document rename)!") FileService.update_by_id(file.id, {"name": req["name"]})
informs = File2DocumentService.get_by_document_id(doc_id)
if informs:
e, file = FileService.get_by_id(informs[0].file_id)
FileService.update_by_id(file.id, {"name": req["name"]})
except Exception as e:
return server_error_response(e)
if "parser_method" in req: if "parser_method" in req:
try: if doc.parser_id.lower() == req["parser_method"].lower():
if doc.parser_id.lower() == req["parser_method"].lower():
if "parser_config" in req:
if req["parser_config"] == doc.parser_config:
return get_json_result(data=True)
else:
return get_json_result(data=True)
if doc.type == FileType.VISUAL or re.search(
r"\.(ppt|pptx|pages)$", doc.name):
return get_data_error_result(retmsg="Not supported yet!")
e = DocumentService.update_by_id(doc.id,
{"parser_id": req["parser_method"], "progress": 0, "progress_msg": "",
"run": TaskStatus.UNSTART.value})
if not e:
return get_data_error_result(retmsg="Document not found!")
if "parser_config" in req:
DocumentService.update_parser_config(doc.id, req["parser_config"])
if doc.token_num > 0:
e = DocumentService.increment_chunk_num(doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1,
doc.process_duation * -1)
if not e:
return get_data_error_result(retmsg="Document not found!")
tenant_id = DocumentService.get_tenant_id(req["id"])
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
except Exception as e:
return server_error_response(e)
return get_json_result(data=True)
@manager.route('/change_parser', methods=['POST'])
@token_required
def change_parser(tenant_id):
req = request.json
try:
e, doc = DocumentService.get_by_id(req["doc_id"])
if not e:
return get_data_error_result(retmsg="Document not found!")
if doc.parser_id.lower() == req["parser_id"].lower():
if "parser_config" in req: if "parser_config" in req:
if req["parser_config"] == doc.parser_config: if req["parser_config"] == doc.parser_config:
return get_json_result(data=True) return get_result(retcode=RetCode.SUCCESS)
else: else:
return get_json_result(data=True) return get_result(retcode=RetCode.SUCCESS)
if doc.type == FileType.VISUAL or re.search( if doc.type == FileType.VISUAL or re.search(
r"\.(ppt|pptx|pages)$", doc.name): r"\.(ppt|pptx|pages)$", doc.name):
return get_data_error_result(retmsg="Not supported yet!") return get_error_data_result(retmsg="Not supported yet!")
e = DocumentService.update_by_id(doc.id, e = DocumentService.update_by_id(doc.id,
{"parser_id": req["parser_id"], "progress": 0, "progress_msg": "", {"parser_id": req["parser_method"], "progress": 0, "progress_msg": "",
"run": TaskStatus.UNSTART.value}) "run": TaskStatus.UNSTART.value})
if not e: if not e:
return get_data_error_result(retmsg="Document not found!") return get_error_data_result(retmsg="Document not found!")
if "parser_config" in req:
DocumentService.update_parser_config(doc.id, req["parser_config"])
if doc.token_num > 0: if doc.token_num > 0:
e = DocumentService.increment_chunk_num(doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1, e = DocumentService.increment_chunk_num(doc.id, doc.kb_id, doc.token_num * -1, doc.chunk_num * -1,
doc.process_duation * -1) doc.process_duation * -1)
if not e: if not e:
return get_data_error_result(retmsg="Document not found!") return get_error_data_result(retmsg="Document not found!")
tenant_id = DocumentService.get_tenant_id(req["doc_id"]) tenant_id = DocumentService.get_tenant_id(req["id"])
if not tenant_id: if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!") return get_error_data_result(retmsg="Tenant not found!")
ELASTICSEARCH.deleteByQuery( ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id)) Q("match", doc_id=doc.id), idxnm=search.index_name(tenant_id))
if "parser_config" in req:
DocumentService.update_parser_config(doc.id, req["parser_config"])
return get_json_result(data=True) return get_result()
except Exception as e:
return server_error_response(e)
@manager.route('/rename', methods=['POST'])
@login_required
@validate_request("doc_id", "name")
def rename():
req = request.json
try:
e, doc = DocumentService.get_by_id(req["doc_id"])
if not e:
return get_data_error_result(retmsg="Document not found!")
if pathlib.Path(req["name"].lower()).suffix != pathlib.Path(
doc.name.lower()).suffix:
return get_json_result(
data=False,
retmsg="The extension of file can't be changed",
retcode=RetCode.ARGUMENT_ERROR)
for d in DocumentService.query(name=req["name"], kb_id=doc.kb_id):
if d.name == req["name"]:
return get_data_error_result(
retmsg="Duplicated document name in the same knowledgebase.")
if not DocumentService.update_by_id(
req["doc_id"], {"name": req["name"]}):
return get_data_error_result(
retmsg="Database error (Document rename)!")
informs = File2DocumentService.get_by_document_id(req["doc_id"])
if informs:
e, file = FileService.get_by_id(informs[0].file_id)
FileService.update_by_id(file.id, {"name": req["name"]})
return get_json_result(data=True)
except Exception as e:
return server_error_response(e)
@manager.route("/<document_id>", methods=["GET"]) @manager.route('/dataset/<dataset_id>/document/<document_id>', methods=['GET'])
@token_required @token_required
def download_document(document_id,tenant_id): def download(tenant_id, dataset_id, document_id):
try: if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
# Check whether there is this document return get_error_data_result(retmsg=f'You do not own the dataset {dataset_id}.')
exist, document = DocumentService.get_by_id(document_id) doc = DocumentService.query(kb_id=dataset_id, id=document_id)
if not exist: if not doc:
return construct_json_result(message=f"This document '{document_id}' cannot be found!", return get_error_data_result(retmsg=f'The dataset not own the document {doc.id}.')
code=RetCode.ARGUMENT_ERROR) # The process of downloading
doc_id, doc_location = File2DocumentService.get_storage_address(doc_id=document_id) # minio address
# The process of downloading file_stream = STORAGE_IMPL.get(doc_id, doc_location)
doc_id, doc_location = File2DocumentService.get_storage_address(doc_id=document_id) # minio address if not file_stream:
file_stream = STORAGE_IMPL.get(doc_id, doc_location) return construct_json_result(message="This file is empty.", code=RetCode.DATA_ERROR)
if not file_stream: file = BytesIO(file_stream)
return construct_json_result(message="This file is empty.", code=RetCode.DATA_ERROR) # Use send_file with a proper filename and MIME type
return send_file(
file = BytesIO(file_stream) file,
as_attachment=True,
# Use send_file with a proper filename and MIME type download_name=doc[0].name,
return send_file( mimetype='application/octet-stream' # Set a default MIME type
file, )
as_attachment=True,
download_name=document.name,
mimetype='application/octet-stream' # Set a default MIME type
)
# Error
except Exception as e:
return construct_error_response(e)
@manager.route('/dataset/<dataset_id>/documents', methods=['GET']) @manager.route('/dataset/<dataset_id>/info', methods=['GET'])
@token_required @token_required
def list_docs(dataset_id, tenant_id): def list_docs(dataset_id, tenant_id):
kb_id = request.args.get("knowledgebase_id") if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
if not kb_id: return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}. ")
return get_json_result( id = request.args.get("id")
data=False, retmsg='Lack of "KB ID"', retcode=RetCode.ARGUMENT_ERROR) if not DocumentService.query(id=id,kb_id=dataset_id):
tenants = UserTenantService.query(user_id=tenant_id) return get_error_data_result(retmsg=f"You don't own the document {id}.")
for tenant in tenants: offset = int(request.args.get("offset", 1))
if KnowledgebaseService.query( keywords = request.args.get("keywords","")
tenant_id=tenant.tenant_id, id=kb_id): limit = int(request.args.get("limit", 1024))
break
else:
return get_json_result(
data=False, retmsg=f'Only owner of knowledgebase authorized for this operation.',
retcode=RetCode.OPERATING_ERROR)
keywords = request.args.get("keywords", "")
page_number = int(request.args.get("page", 1))
items_per_page = int(request.args.get("page_size", 15))
orderby = request.args.get("orderby", "create_time") orderby = request.args.get("orderby", "create_time")
desc = request.args.get("desc", True) if request.args.get("desc") == "False":
try: desc = False
docs, tol = DocumentService.get_by_kb_id( else:
kb_id, page_number, items_per_page, orderby, desc, keywords) desc = True
docs, tol = DocumentService.get_list(dataset_id, offset, limit, orderby, desc, keywords, id)
# rename key's name # rename key's name
renamed_doc_list = [] renamed_doc_list = []
for doc in docs: for doc in docs:
key_mapping = { key_mapping = {
"chunk_num": "chunk_count", "chunk_num": "chunk_count",
"kb_id": "knowledgebase_id", "kb_id": "knowledgebase_id",
"token_num": "token_count", "token_num": "token_count",
"parser_id":"parser_method" "parser_id": "parser_method"
} }
renamed_doc = {} renamed_doc = {}
for key, value in doc.items(): for key, value in doc.items():
new_key = key_mapping.get(key, key) new_key = key_mapping.get(key, key)
renamed_doc[new_key] = value renamed_doc[new_key] = value
renamed_doc_list.append(renamed_doc) renamed_doc_list.append(renamed_doc)
return get_json_result(data={"total": tol, "docs": renamed_doc_list}) return get_result(data={"total": tol, "docs": renamed_doc_list})
except Exception as e:
return server_error_response(e)
@manager.route('/delete', methods=['DELETE']) @manager.route('/dataset/<dataset_id>/document', methods=['DELETE'])
@token_required @token_required
def rm(tenant_id): def delete(tenant_id,dataset_id):
req = request.args if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
if "document_id" not in req: return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}. ")
return get_data_error_result( req = request.json
retmsg="doc_id is required") if not req.get("ids"):
doc_ids = req["document_id"] return get_error_data_result(retmsg="ids is required")
if isinstance(doc_ids, str): doc_ids = [doc_ids] doc_ids = req["ids"]
root_folder = FileService.get_root_folder(tenant_id) root_folder = FileService.get_root_folder(tenant_id)
pf_id = root_folder["id"] pf_id = root_folder["id"]
FileService.init_knowledgebase_docs(pf_id, tenant_id) FileService.init_knowledgebase_docs(pf_id, tenant_id)
@ -368,15 +226,15 @@ def rm(tenant_id):
try: try:
e, doc = DocumentService.get_by_id(doc_id) e, doc = DocumentService.get_by_id(doc_id)
if not e: if not e:
return get_data_error_result(retmsg="Document not found!") return get_error_data_result(retmsg="Document not found!")
tenant_id = DocumentService.get_tenant_id(doc_id) tenant_id = DocumentService.get_tenant_id(doc_id)
if not tenant_id: if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!") return get_error_data_result(retmsg="Tenant not found!")
b, n = File2DocumentService.get_storage_address(doc_id=doc_id) b, n = File2DocumentService.get_storage_address(doc_id=doc_id)
if not DocumentService.remove_document(doc, tenant_id): if not DocumentService.remove_document(doc, tenant_id):
return get_data_error_result( return get_error_data_result(
retmsg="Database error (Document removal)!") retmsg="Database error (Document removal)!")
f2d = File2DocumentService.get_by_document_id(doc_id) f2d = File2DocumentService.get_by_document_id(doc_id)
@ -388,80 +246,69 @@ def rm(tenant_id):
errors += str(e) errors += str(e)
if errors: if errors:
return get_json_result(data=False, retmsg=errors, retcode=RetCode.SERVER_ERROR) return get_result(retmsg=errors, retcode=RetCode.SERVER_ERROR)
return get_json_result(data=True, retmsg="success") return get_result()
@manager.route("/<document_id>/status", methods=["GET"])
@manager.route('/dataset/<dataset_id>/chunk', methods=['POST'])
@token_required @token_required
def show_parsing_status(tenant_id, document_id): def parse(tenant_id,dataset_id):
try: if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
# valid document return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}.")
exist, _ = DocumentService.get_by_id(document_id)
if not exist:
return construct_json_result(code=RetCode.DATA_ERROR,
message=f"This document: '{document_id}' is not a valid document.")
_, doc = DocumentService.get_by_id(document_id) # get doc object
doc_attributes = doc.to_dict()
return construct_json_result(
data={"progress": doc_attributes["progress"], "status": TaskStatus(doc_attributes["status"]).name},
code=RetCode.SUCCESS
)
except Exception as e:
return construct_error_response(e)
@manager.route('/run', methods=['POST'])
@token_required
def run(tenant_id):
req = request.json req = request.json
try: for id in req["document_ids"]:
for id in req["document_ids"]: if not DocumentService.query(id=id,kb_id=dataset_id):
info = {"run": str(req["run"]), "progress": 0} return get_error_data_result(retmsg=f"You don't own the document {id}.")
if str(req["run"]) == TaskStatus.RUNNING.value: info = {"run": "1", "progress": 0}
info["progress_msg"] = "" info["progress_msg"] = ""
info["chunk_num"] = 0 info["chunk_num"] = 0
info["token_num"] = 0 info["token_num"] = 0
DocumentService.update_by_id(id, info) DocumentService.update_by_id(id, info)
# if str(req["run"]) == TaskStatus.CANCEL.value: # if str(req["run"]) == TaskStatus.CANCEL.value:
tenant_id = DocumentService.get_tenant_id(id) ELASTICSEARCH.deleteByQuery(
if not tenant_id: Q("match", doc_id=id), idxnm=search.index_name(tenant_id))
return get_data_error_result(retmsg="Tenant not found!") TaskService.filter_delete([Task.doc_id == id])
ELASTICSEARCH.deleteByQuery( e, doc = DocumentService.get_by_id(id)
Q("match", doc_id=id), idxnm=search.index_name(tenant_id)) doc = doc.to_dict()
doc["tenant_id"] = tenant_id
bucket, name = File2DocumentService.get_storage_address(doc_id=doc["id"])
queue_tasks(doc, bucket, name)
return get_result()
if str(req["run"]) == TaskStatus.RUNNING.value: @manager.route('/dataset/<dataset_id>/chunk', methods=['DELETE'])
TaskService.filter_delete([Task.doc_id == id])
e, doc = DocumentService.get_by_id(id)
doc = doc.to_dict()
doc["tenant_id"] = tenant_id
bucket, name = File2DocumentService.get_storage_address(doc_id=doc["id"])
queue_tasks(doc, bucket, name)
return get_json_result(data=True)
except Exception as e:
return server_error_response(e)
@manager.route('/chunk/list', methods=['POST'])
@token_required @token_required
@validate_request("document_id") def stop_parsing(tenant_id,dataset_id):
def list_chunk(tenant_id): if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}.")
req = request.json req = request.json
doc_id = req["document_id"] for id in req["document_ids"]:
page = int(req.get("page", 1)) if not DocumentService.query(id=id,kb_id=dataset_id):
size = int(req.get("size", 30)) return get_error_data_result(retmsg=f"You don't own the document {id}.")
info = {"run": "2", "progress": 0}
DocumentService.update_by_id(id, info)
# if str(req["run"]) == TaskStatus.CANCEL.value:
tenant_id = DocumentService.get_tenant_id(id)
ELASTICSEARCH.deleteByQuery(
Q("match", doc_id=id), idxnm=search.index_name(tenant_id))
return get_result()
@manager.route('/dataset/{dataset_id}/document/{document_id}/chunk', methods=['GET'])
@token_required
def list_chunk(tenant_id,dataset_id,document_id):
if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}.")
doc=DocumentService.query(id=document_id, kb_id=dataset_id)
if not doc:
return get_error_data_result(retmsg=f"You don't own the document {document_id}.")
doc=doc[0]
req = request.args
doc_id = document_id
page = int(req.get("offset", 1))
size = int(req.get("limit", 30))
question = req.get("keywords", "") question = req.get("keywords", "")
try: try:
tenant_id = DocumentService.get_tenant_id(req["document_id"])
if not tenant_id:
return get_data_error_result(retmsg="Tenant not found!")
e, doc = DocumentService.get_by_id(doc_id)
if not e:
return get_data_error_result(retmsg="Document not found!")
query = { query = {
"doc_ids": [doc_id], "page": page, "size": size, "question": question, "sort": True "doc_ids": [doc_id], "page": page, "size": size, "question": question, "sort": True
} }
@ -470,7 +317,7 @@ def list_chunk(tenant_id):
sres = retrievaler.search(query, search.index_name(tenant_id), highlight=True) sres = retrievaler.search(query, search.index_name(tenant_id), highlight=True)
res = {"total": sres.total, "chunks": [], "doc": doc.to_dict()} res = {"total": sres.total, "chunks": [], "doc": doc.to_dict()}
origin_chunks=[] origin_chunks = []
for id in sres.ids: for id in sres.ids:
d = { d = {
"chunk_id": id, "chunk_id": id,
@ -490,7 +337,7 @@ def list_chunk(tenant_id):
poss.append([float(d["positions"][i]), float(d["positions"][i + 1]), float(d["positions"][i + 2]), poss.append([float(d["positions"][i]), float(d["positions"][i + 1]), float(d["positions"][i + 2]),
float(d["positions"][i + 3]), float(d["positions"][i + 4])]) float(d["positions"][i + 3]), float(d["positions"][i + 4])])
d["positions"] = poss d["positions"] = poss
origin_chunks.append(d) origin_chunks.append(d)
##rename keys ##rename keys
for chunk in origin_chunks: for chunk in origin_chunks:
@ -499,28 +346,34 @@ def list_chunk(tenant_id):
"content_with_weight": "content", "content_with_weight": "content",
"doc_id": "document_id", "doc_id": "document_id",
"important_kwd": "important_keywords", "important_kwd": "important_keywords",
"img_id":"image_id", "img_id": "image_id",
} }
renamed_chunk = {} renamed_chunk = {}
for key, value in chunk.items(): for key, value in chunk.items():
new_key = key_mapping.get(key, key) new_key = key_mapping.get(key, key)
renamed_chunk[new_key] = value renamed_chunk[new_key] = value
res["chunks"].append(renamed_chunk) res["chunks"].append(renamed_chunk)
return get_json_result(data=res) return get_result(data=res)
except Exception as e: except Exception as e:
if str(e).find("not_found") > 0: if str(e).find("not_found") > 0:
return get_json_result(data=False, retmsg=f'No chunk found!', return get_result(retmsg=f'No chunk found!',
retcode=RetCode.DATA_ERROR) retcode=RetCode.DATA_ERROR)
return server_error_response(e) return server_error_response(e)
@manager.route('/chunk/create', methods=['POST']) @manager.route('/dataset/{dataset_id}/document/{document_id}/chunk', methods=['POST'])
@token_required @token_required
@validate_request("document_id", "content") def create(tenant_id,dataset_id,document_id):
def create(tenant_id): if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}.")
doc = DocumentService.query(id=document_id, kb_id=dataset_id)
if not doc:
return get_error_data_result(retmsg=f"You don't own the document {document_id}.")
req = request.json req = request.json
if not req.get("content"):
return get_error_data_result(retmsg="`content` is required")
md5 = hashlib.md5() md5 = hashlib.md5()
md5.update((req["content"] + req["document_id"]).encode("utf-8")) md5.update((req["content"] + document_id).encode("utf-8"))
chunk_id = md5.hexdigest() chunk_id = md5.hexdigest()
d = {"id": chunk_id, "content_ltks": rag_tokenizer.tokenize(req["content"]), d = {"id": chunk_id, "content_ltks": rag_tokenizer.tokenize(req["content"]),
@ -530,80 +383,77 @@ def create(tenant_id):
d["important_tks"] = rag_tokenizer.tokenize(" ".join(req.get("important_kwd", []))) d["important_tks"] = rag_tokenizer.tokenize(" ".join(req.get("important_kwd", [])))
d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19] d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19]
d["create_timestamp_flt"] = datetime.datetime.now().timestamp() d["create_timestamp_flt"] = datetime.datetime.now().timestamp()
d["kb_id"] = [doc.kb_id]
d["docnm_kwd"] = doc.name
d["doc_id"] = doc.id
embd_id = DocumentService.get_embd_id(document_id)
embd_mdl = TenantLLMService.model_instance(
tenant_id, LLMType.EMBEDDING.value, embd_id)
try: v, c = embd_mdl.encode([doc.name, req["content"]])
e, doc = DocumentService.get_by_id(req["document_id"]) v = 0.1 * v[0] + 0.9 * v[1]
if not e: d["q_%d_vec" % len(v)] = v.tolist()
return get_data_error_result(retmsg="Document not found!") ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
d["kb_id"] = [doc.kb_id]
d["docnm_kwd"] = doc.name
d["doc_id"] = doc.id
tenant_id = DocumentService.get_tenant_id(req["document_id"]) DocumentService.increment_chunk_num(
if not tenant_id: doc.id, doc.kb_id, c, 1, 0)
return get_data_error_result(retmsg="Tenant not found!") d["chunk_id"] = chunk_id
# rename keys
key_mapping = {
"chunk_id": "id",
"content_with_weight": "content",
"doc_id": "document_id",
"important_kwd": "important_keywords",
"kb_id": "dataset_id",
"create_timestamp_flt": "create_timestamp",
"create_time": "create_time",
"document_keyword": "document",
}
renamed_chunk = {}
for key, value in d.items():
if key in key_mapping:
new_key = key_mapping.get(key, key)
renamed_chunk[new_key] = value
return get_result(data={"chunk": renamed_chunk})
# return get_result(data={"chunk_id": chunk_id})
embd_id = DocumentService.get_embd_id(req["document_id"])
embd_mdl = TenantLLMService.model_instance(
tenant_id, LLMType.EMBEDDING.value, embd_id)
v, c = embd_mdl.encode([doc.name, req["content"]]) @manager.route('dataset/{dataset_id}/document/{document_id}/chunk', methods=['DELETE'])
v = 0.1 * v[0] + 0.9 * v[1]
d["q_%d_vec" % len(v)] = v.tolist()
ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
DocumentService.increment_chunk_num(
doc.id, doc.kb_id, c, 1, 0)
d["chunk_id"] = chunk_id
#rename keys
key_mapping = {
"chunk_id": "id",
"content_with_weight": "content",
"doc_id": "document_id",
"important_kwd": "important_keywords",
"kb_id":"dataset_id",
"create_timestamp_flt":"create_timestamp",
"create_time": "create_time",
"document_keyword":"document",
}
renamed_chunk = {}
for key, value in d.items():
if key in key_mapping:
new_key = key_mapping.get(key, key)
renamed_chunk[new_key] = value
return get_json_result(data={"chunk": renamed_chunk})
# return get_json_result(data={"chunk_id": chunk_id})
except Exception as e:
return server_error_response(e)
@manager.route('/chunk/rm', methods=['POST'])
@token_required @token_required
@validate_request("chunk_ids", "document_id") def rm_chunk(tenant_id,dataset_id,document_id):
def rm_chunk(tenant_id): if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}.")
doc = DocumentService.query(id=document_id, kb_id=dataset_id)
if not doc:
return get_error_data_result(retmsg=f"You don't own the document {document_id}.")
req = request.json req = request.json
try: if not req.get("chunk_ids"):
if not ELASTICSEARCH.deleteByQuery( return get_error_data_result("`chunk_ids` is required")
Q("ids", values=req["chunk_ids"]), search.index_name(tenant_id)): if not ELASTICSEARCH.deleteByQuery(
return get_data_error_result(retmsg="Index updating failure") Q("ids", values=req["chunk_ids"]), search.index_name(tenant_id)):
e, doc = DocumentService.get_by_id(req["document_id"]) return get_error_data_result(retmsg="Index updating failure")
if not e: deleted_chunk_ids = req["chunk_ids"]
return get_data_error_result(retmsg="Document not found!") chunk_number = len(deleted_chunk_ids)
deleted_chunk_ids = req["chunk_ids"] DocumentService.decrement_chunk_num(doc.id, doc.kb_id, 1, chunk_number, 0)
chunk_number = len(deleted_chunk_ids) return get_result()
DocumentService.decrement_chunk_num(doc.id, doc.kb_id, 1, chunk_number, 0)
return get_json_result(data=True)
except Exception as e:
return server_error_response(e)
@manager.route('/chunk/set', methods=['POST'])
@manager.route('/dataset/{dataset_id}/document/{document_id}/chunk/{chunk_id}', methods=['PUT'])
@token_required @token_required
@validate_request("document_id", "chunk_id", "content", def set(tenant_id,dataset_id,document_id,chunk_id):
"important_keywords") if not KnowledgebaseService.query(id=dataset_id, tenant_id=tenant_id):
def set(tenant_id): return get_error_data_result(retmsg=f"You don't own the dataset {dataset_id}.")
doc = DocumentService.query(id=document_id, kb_id=dataset_id)
if not doc:
return get_error_data_result(retmsg=f"You don't own the document {document_id}.")
req = request.json req = request.json
if not req.get("content"):
return get_error_data_result("`content` is required")
if not req.get("important_keywords"):
return get_error_data_result("`important_keywords` is required")
d = { d = {
"id": req["chunk_id"], "id": chunk_id,
"content_with_weight": req["content"]} "content_with_weight": req["content"]}
d["content_ltks"] = rag_tokenizer.tokenize(req["content"]) d["content_ltks"] = rag_tokenizer.tokenize(req["content"])
d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"]) d["content_sm_ltks"] = rag_tokenizer.fine_grained_tokenize(d["content_ltks"])
@ -611,71 +461,54 @@ def set(tenant_id):
d["important_tks"] = rag_tokenizer.tokenize(" ".join(req["important_keywords"])) d["important_tks"] = rag_tokenizer.tokenize(" ".join(req["important_keywords"]))
if "available" in req: if "available" in req:
d["available_int"] = req["available"] d["available_int"] = req["available"]
embd_id = DocumentService.get_embd_id(document_id)
embd_mdl = TenantLLMService.model_instance(
tenant_id, LLMType.EMBEDDING.value, embd_id)
if doc.parser_id == ParserType.QA:
arr = [
t for t in re.split(
r"[\n\t]",
req["content"]) if len(t) > 1]
if len(arr) != 2:
return get_error_data_result(
retmsg="Q&A must be separated by TAB/ENTER key.")
q, a = rmPrefix(arr[0]), rmPrefix(arr[1])
d = beAdoc(d, arr[0], arr[1], not any(
[rag_tokenizer.is_chinese(t) for t in q + a]))
try: v, c = embd_mdl.encode([doc.name, req["content"]])
tenant_id = DocumentService.get_tenant_id(req["document_id"]) v = 0.1 * v[0] + 0.9 * v[1] if doc.parser_id != ParserType.QA else v[1]
if not tenant_id: d["q_%d_vec" % len(v)] = v.tolist()
return get_data_error_result(retmsg="Tenant not found!") ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
return get_result()
embd_id = DocumentService.get_embd_id(req["document_id"])
embd_mdl = TenantLLMService.model_instance(
tenant_id, LLMType.EMBEDDING.value, embd_id)
e, doc = DocumentService.get_by_id(req["document_id"])
if not e:
return get_data_error_result(retmsg="Document not found!")
if doc.parser_id == ParserType.QA: @manager.route('/retrieval', methods=['GET'])
arr = [
t for t in re.split(
r"[\n\t]",
req["content"]) if len(t) > 1]
if len(arr) != 2:
return get_data_error_result(
retmsg="Q&A must be separated by TAB/ENTER key.")
q, a = rmPrefix(arr[0]), rmPrefix(arr[1])
d = beAdoc(d, arr[0], arr[1], not any(
[rag_tokenizer.is_chinese(t) for t in q + a]))
v, c = embd_mdl.encode([doc.name, req["content"]])
v = 0.1 * v[0] + 0.9 * v[1] if doc.parser_id != ParserType.QA else v[1]
d["q_%d_vec" % len(v)] = v.tolist()
ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
return get_json_result(data=True)
except Exception as e:
return server_error_response(e)
@manager.route('/retrieval_test', methods=['POST'])
@token_required @token_required
@validate_request("knowledgebase_id", "question")
def retrieval_test(tenant_id): def retrieval_test(tenant_id):
req = request.json req = request.args
page = int(req.get("page", 1)) if not req.get("datasets"):
size = int(req.get("size", 30)) return get_error_data_result("`datasets` is required.")
for id in req.get("datasets"):
if not KnowledgebaseService.query(id=id,tenant_id=tenant_id):
return get_error_data_result(f"You don't own the dataset {id}.")
if not req.get("question"):
return get_error_data_result("`question` is required.")
page = int(req.get("offset", 1))
size = int(req.get("limit", 30))
question = req["question"] question = req["question"]
kb_id = req["knowledgebase_id"] kb_id = req["datasets"]
if isinstance(kb_id, str): kb_id = [kb_id] if isinstance(kb_id, str): kb_id = [kb_id]
doc_ids = req.get("doc_ids", []) doc_ids = req.get("documents", [])
similarity_threshold = float(req.get("similarity_threshold", 0.2)) similarity_threshold = float(req.get("similarity_threshold", 0.2))
vector_similarity_weight = float(req.get("vector_similarity_weight", 0.3)) vector_similarity_weight = float(req.get("vector_similarity_weight", 0.3))
top = int(req.get("top_k", 1024)) top = int(req.get("top_k", 1024))
try: try:
tenants = UserTenantService.query(user_id=tenant_id)
for kid in kb_id:
for tenant in tenants:
if KnowledgebaseService.query(
tenant_id=tenant.tenant_id, id=kid):
break
else:
return get_json_result(
data=False, retmsg=f'Only owner of knowledgebase authorized for this operation.',
retcode=RetCode.OPERATING_ERROR)
e, kb = KnowledgebaseService.get_by_id(kb_id[0]) e, kb = KnowledgebaseService.get_by_id(kb_id[0])
if not e: if not e:
return get_data_error_result(retmsg="Knowledgebase not found!") return get_error_data_result(retmsg="Knowledgebase not found!")
embd_mdl = TenantLLMService.model_instance( embd_mdl = TenantLLMService.model_instance(
kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id) kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id)
@ -697,24 +530,24 @@ def retrieval_test(tenant_id):
del c["vector"] del c["vector"]
##rename keys ##rename keys
renamed_chunks=[] renamed_chunks = []
for chunk in ranks["chunks"]: for chunk in ranks["chunks"]:
key_mapping = { key_mapping = {
"chunk_id": "id", "chunk_id": "id",
"content_with_weight": "content", "content_with_weight": "content",
"doc_id": "document_id", "doc_id": "document_id",
"important_kwd": "important_keywords", "important_kwd": "important_keywords",
"docnm_kwd":"document_keyword" "docnm_kwd": "document_keyword"
} }
rename_chunk={} rename_chunk = {}
for key, value in chunk.items(): for key, value in chunk.items():
new_key = key_mapping.get(key, key) new_key = key_mapping.get(key, key)
rename_chunk[new_key] = value rename_chunk[new_key] = value
renamed_chunks.append(rename_chunk) renamed_chunks.append(rename_chunk)
ranks["chunks"] = renamed_chunks ranks["chunks"] = renamed_chunks
return get_json_result(data=ranks) return get_result(data=ranks)
except Exception as e: except Exception as e:
if str(e).find("not_found") > 0: if str(e).find("not_found") > 0:
return get_json_result(data=False, retmsg=f'No chunk found! Check the chunk status please!', return get_result(retmsg=f'No chunk found! Check the chunk status please!',
retcode=RetCode.DATA_ERROR) retcode=RetCode.DATA_ERROR)
return server_error_response(e) return server_error_response(e)

View File

@ -20,47 +20,18 @@ from flask import request, Response
from api.db import StatusEnum from api.db import StatusEnum
from api.db.services.dialog_service import DialogService, ConversationService, chat from api.db.services.dialog_service import DialogService, ConversationService, chat
from api.settings import RetCode
from api.utils import get_uuid from api.utils import get_uuid
from api.utils.api_utils import get_data_error_result from api.utils.api_utils import get_error_data_result
from api.utils.api_utils import get_json_result, token_required from api.utils.api_utils import get_result, token_required
@manager.route('/chat/<chat_id>/session', methods=['POST'])
@manager.route('/save', methods=['POST'])
@token_required @token_required
def set_conversation(tenant_id): def create(tenant_id,chat_id):
req = request.json req = request.json
conv_id = req.get("id") req["dialog_id"] = chat_id
if "assistant_id" in req:
req["dialog_id"] = req.pop("assistant_id")
if "id" in req:
del req["id"]
conv = ConversationService.query(id=conv_id)
if not conv:
return get_data_error_result(retmsg="Session does not exist")
if not DialogService.query(id=conv[0].dialog_id, tenant_id=tenant_id, status=StatusEnum.VALID.value):
return get_data_error_result(retmsg="You do not own the session")
if req.get("dialog_id"):
dia = DialogService.query(tenant_id=tenant_id, id=req["dialog_id"], status=StatusEnum.VALID.value)
if not dia:
return get_data_error_result(retmsg="You do not own the assistant")
if "dialog_id" in req and not req.get("dialog_id"):
return get_data_error_result(retmsg="assistant_id can not be empty.")
if "message" in req:
return get_data_error_result(retmsg="message can not be change")
if "reference" in req:
return get_data_error_result(retmsg="reference can not be change")
if "name" in req and not req.get("name"):
return get_data_error_result(retmsg="name can not be empty.")
if not ConversationService.update_by_id(conv_id, req):
return get_data_error_result(retmsg="Session updates error")
return get_json_result(data=True)
if not req.get("dialog_id"):
return get_data_error_result(retmsg="assistant_id is required.")
dia = DialogService.query(tenant_id=tenant_id, id=req["dialog_id"], status=StatusEnum.VALID.value) dia = DialogService.query(tenant_id=tenant_id, id=req["dialog_id"], status=StatusEnum.VALID.value)
if not dia: if not dia:
return get_data_error_result(retmsg="You do not own the assistant") return get_error_data_result(retmsg="You do not own the assistant")
conv = { conv = {
"id": get_uuid(), "id": get_uuid(),
"dialog_id": req["dialog_id"], "dialog_id": req["dialog_id"],
@ -68,33 +39,58 @@ def set_conversation(tenant_id):
"message": [{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}] "message": [{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}]
} }
if not conv.get("name"): if not conv.get("name"):
return get_data_error_result(retmsg="name can not be empty.") return get_error_data_result(retmsg="Name can not be empty.")
ConversationService.save(**conv) ConversationService.save(**conv)
e, conv = ConversationService.get_by_id(conv["id"]) e, conv = ConversationService.get_by_id(conv["id"])
if not e: if not e:
return get_data_error_result(retmsg="Fail to new session!") return get_error_data_result(retmsg="Fail to create a session!")
conv = conv.to_dict() conv = conv.to_dict()
conv['messages'] = conv.pop("message") conv['messages'] = conv.pop("message")
conv["assistant_id"] = conv.pop("dialog_id") conv["chat_id"] = conv.pop("dialog_id")
del conv["reference"] del conv["reference"]
return get_json_result(data=conv) return get_result(data=conv)
@manager.route('/chat/<chat_id>/session/<session_id>', methods=['PUT'])
@manager.route('/completion', methods=['POST'])
@token_required @token_required
def completion(tenant_id): def update(tenant_id,chat_id,session_id):
req = request.json
if "dialog_id" in req and req.get("dialog_id") != chat_id:
return get_error_data_result(retmsg="Can't change chat_id")
if "chat_id" in req and req.get("chat_id") != chat_id:
return get_error_data_result(retmsg="Can't change chat_id")
req["dialog_id"] = chat_id
conv_id = session_id
conv = ConversationService.query(id=conv_id,dialog_id=chat_id)
if not conv:
return get_error_data_result(retmsg="Session does not exist")
if not DialogService.query(id=chat_id, tenant_id=tenant_id, status=StatusEnum.VALID.value):
return get_error_data_result(retmsg="You do not own the session")
if "message" in req or "messages" in req:
return get_error_data_result(retmsg="Message can not be change")
if "reference" in req:
return get_error_data_result(retmsg="Reference can not be change")
if "name" in req and not req.get("name"):
return get_error_data_result(retmsg="Name can not be empty.")
if not ConversationService.update_by_id(conv_id, req):
return get_error_data_result(retmsg="Session updates error")
return get_result()
@manager.route('/chat/<chat_id>/session/<session_id>/completion', methods=['POST'])
@token_required
def completion(tenant_id,chat_id,session_id):
req = request.json req = request.json
# req = {"conversation_id": "9aaaca4c11d311efa461fa163e197198", "messages": [ # req = {"conversation_id": "9aaaca4c11d311efa461fa163e197198", "messages": [
# {"role": "user", "content": "上海有吗?"} # {"role": "user", "content": "上海有吗?"}
# ]} # ]}
if "session_id" not in req: if not req.get("question"):
return get_data_error_result(retmsg="session_id is required") return get_error_data_result(retmsg="Please input your question.")
conv = ConversationService.query(id=req["session_id"]) conv = ConversationService.query(id=session_id,dialog_id=chat_id)
if not conv: if not conv:
return get_data_error_result(retmsg="Session does not exist") return get_error_data_result(retmsg="Session does not exist")
conv = conv[0] conv = conv[0]
if not DialogService.query(id=conv.dialog_id, tenant_id=tenant_id, status=StatusEnum.VALID.value): if not DialogService.query(id=chat_id, tenant_id=tenant_id, status=StatusEnum.VALID.value):
return get_data_error_result(retmsg="You do not own the session") return get_error_data_result(retmsg="You do not own the session")
msg = [] msg = []
question = { question = {
"content": req.get("question"), "content": req.get("question"),
@ -108,7 +104,6 @@ def completion(tenant_id):
msg.append(m) msg.append(m)
message_id = msg[-1].get("id") message_id = msg[-1].get("id")
e, dia = DialogService.get_by_id(conv.dialog_id) e, dia = DialogService.get_by_id(conv.dialog_id)
del req["session_id"]
if not conv.reference: if not conv.reference:
conv.reference = [] conv.reference = []
@ -130,13 +125,13 @@ def completion(tenant_id):
try: try:
for ans in chat(dia, msg, **req): for ans in chat(dia, msg, **req):
fillin_conv(ans) fillin_conv(ans)
yield "data:" + json.dumps({"retcode": 0, "retmsg": "", "data": ans}, ensure_ascii=False) + "\n\n" yield "data:" + json.dumps({"code": 0, "data": ans}, ensure_ascii=False) + "\n\n"
ConversationService.update_by_id(conv.id, conv.to_dict()) ConversationService.update_by_id(conv.id, conv.to_dict())
except Exception as e: except Exception as e:
yield "data:" + json.dumps({"retcode": 500, "retmsg": str(e), yield "data:" + json.dumps({"code": 500, "message": str(e),
"data": {"answer": "**ERROR**: " + str(e), "reference": []}}, "data": {"answer": "**ERROR**: " + str(e), "reference": []}},
ensure_ascii=False) + "\n\n" ensure_ascii=False) + "\n\n"
yield "data:" + json.dumps({"retcode": 0, "retmsg": "", "data": True}, ensure_ascii=False) + "\n\n" yield "data:" + json.dumps({"code": 0, "data": True}, ensure_ascii=False) + "\n\n"
if req.get("stream", True): if req.get("stream", True):
resp = Response(stream(), mimetype="text/event-stream") resp = Response(stream(), mimetype="text/event-stream")
@ -153,73 +148,31 @@ def completion(tenant_id):
fillin_conv(ans) fillin_conv(ans)
ConversationService.update_by_id(conv.id, conv.to_dict()) ConversationService.update_by_id(conv.id, conv.to_dict())
break break
return get_json_result(data=answer) return get_result(data=answer)
@manager.route('/chat/<chat_id>/session', methods=['GET'])
@manager.route('/get', methods=['GET'])
@token_required @token_required
def get(tenant_id): def list(chat_id,tenant_id):
req = request.args if not DialogService.query(tenant_id=tenant_id, id=chat_id, status=StatusEnum.VALID.value):
if "id" not in req: return get_error_data_result(retmsg=f"You don't own the assistant {chat_id}.")
return get_data_error_result(retmsg="id is required") id = request.args.get("id")
conv_id = req["id"] name = request.args.get("name")
conv = ConversationService.query(id=conv_id) session = ConversationService.query(id=id,name=name,dialog_id=chat_id)
if not conv: if not session:
return get_data_error_result(retmsg="Session does not exist") return get_error_data_result(retmsg="The session doesn't exist")
if not DialogService.query(id=conv[0].dialog_id, tenant_id=tenant_id, status=StatusEnum.VALID.value): page_number = int(request.args.get("page", 1))
return get_data_error_result(retmsg="You do not own the session") items_per_page = int(request.args.get("page_size", 1024))
if "assistant_id" in req: orderby = request.args.get("orderby", "create_time")
if req["assistant_id"] != conv[0].dialog_id: if request.args.get("desc") == "False":
return get_data_error_result(retmsg="The session doesn't belong to the assistant") desc = False
conv = conv[0].to_dict() else:
conv['messages'] = conv.pop("message") desc = True
conv["assistant_id"] = conv.pop("dialog_id") convs = ConversationService.get_list(chat_id,page_number,items_per_page,orderby,desc,id,name)
if conv["reference"]: if not convs:
messages = conv["messages"] return get_result(data=[])
message_num = 0
chunk_num = 0
while message_num < len(messages):
if message_num != 0 and messages[message_num]["role"] != "user":
chunk_list = []
if "chunks" in conv["reference"][chunk_num]:
chunks = conv["reference"][chunk_num]["chunks"]
for chunk in chunks:
new_chunk = {
"id": chunk["chunk_id"],
"content": chunk["content_with_weight"],
"document_id": chunk["doc_id"],
"document_name": chunk["docnm_kwd"],
"knowledgebase_id": chunk["kb_id"],
"image_id": chunk["img_id"],
"similarity": chunk["similarity"],
"vector_similarity": chunk["vector_similarity"],
"term_similarity": chunk["term_similarity"],
"positions": chunk["positions"],
}
chunk_list.append(new_chunk)
chunk_num += 1
messages[message_num]["reference"] = chunk_list
message_num += 1
del conv["reference"]
return get_json_result(data=conv)
@manager.route('/list', methods=["GET"])
@token_required
def list(tenant_id):
assistant_id = request.args["assistant_id"]
if not DialogService.query(tenant_id=tenant_id, id=assistant_id, status=StatusEnum.VALID.value):
return get_json_result(
data=False, retmsg=f"You don't own the assistant.",
retcode=RetCode.OPERATING_ERROR)
convs = ConversationService.query(
dialog_id=assistant_id,
order_by=ConversationService.model.create_time,
reverse=True)
convs = [d.to_dict() for d in convs]
for conv in convs: for conv in convs:
conv['messages'] = conv.pop("message") conv['messages'] = conv.pop("message")
conv["assistant_id"] = conv.pop("dialog_id") conv["chat"] = conv.pop("dialog_id")
if conv["reference"]: if conv["reference"]:
messages = conv["messages"] messages = conv["messages"]
message_num = 0 message_num = 0
@ -247,20 +200,19 @@ def list(tenant_id):
messages[message_num]["reference"] = chunk_list messages[message_num]["reference"] = chunk_list
message_num += 1 message_num += 1
del conv["reference"] del conv["reference"]
return get_json_result(data=convs) return get_result(data=convs)
@manager.route('/chat/<chat_id>/session', methods=["DELETE"])
@manager.route('/delete', methods=["DELETE"])
@token_required @token_required
def delete(tenant_id): def delete(tenant_id,chat_id):
id = request.args.get("id") if not DialogService.query(id=chat_id, tenant_id=tenant_id, status=StatusEnum.VALID.value):
if not id: return get_error_data_result(retmsg="You don't own the chat")
return get_data_error_result(retmsg="`id` is required in deleting operation") ids = request.json.get("ids")
conv = ConversationService.query(id=id) if not ids:
if not conv: return get_error_data_result(retmsg="`ids` is required in deleting operation")
return get_data_error_result(retmsg="Session doesn't exist") for id in ids:
conv = conv[0] conv = ConversationService.query(id=id,dialog_id=chat_id)
if not DialogService.query(id=conv.dialog_id, tenant_id=tenant_id, status=StatusEnum.VALID.value): if not conv:
return get_data_error_result(retmsg="You don't own the session") return get_error_data_result(retmsg="The chat doesn't own the session")
ConversationService.delete_by_id(id) ConversationService.delete_by_id(id)
return get_json_result(data=True) return get_result()

View File

@ -19,6 +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,StatusEnum from api.db import LLMType, ParserType,StatusEnum
from api.db.db_models import Dialog, Conversation,DB 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
@ -61,6 +63,22 @@ class DialogService(CommonService):
class ConversationService(CommonService): class ConversationService(CommonService):
model = Conversation model = Conversation
@classmethod
@DB.connection_context()
def get_list(cls,dialog_id,page_number, items_per_page, orderby, desc, id , name):
sessions = cls.model.select().where(cls.model.dialog_id ==dialog_id)
if id:
sessions = sessions.where(cls.model.id == id)
if name:
sessions = sessions.where(cls.model.name == name)
if desc:
sessions = sessions.order_by(cls.model.getter_by(orderby).desc())
else:
sessions = sessions.order_by(cls.model.getter_by(orderby).asc())
sessions = sessions.paginate(page_number, items_per_page)
return list(sessions.dicts())
def message_fit_in(msg, max_length=4000): def message_fit_in(msg, max_length=4000):
def count(): def count():

View File

@ -49,6 +49,29 @@ from rag.utils.redis_conn import REDIS_CONN
class DocumentService(CommonService): class DocumentService(CommonService):
model = Document model = Document
@classmethod
@DB.connection_context()
def get_list(cls, kb_id, page_number, items_per_page,
orderby, desc, keywords, id):
docs =cls.model.select().where(cls.model.kb_id==kb_id)
if id:
docs = docs.where(
cls.model.id== id )
if keywords:
docs = docs.where(
fn.LOWER(cls.model.name).contains(keywords.lower())
)
count = docs.count()
if desc:
docs = docs.order_by(cls.model.getter_by(orderby).desc())
else:
docs = docs.order_by(cls.model.getter_by(orderby).asc())
docs = docs.paginate(page_number, items_per_page)
return list(docs.dicts()), count
@classmethod @classmethod
@DB.connection_context() @DB.connection_context()
def get_by_kb_id(cls, kb_id, page_number, items_per_page, def get_by_kb_id(cls, kb_id, page_number, items_per_page,
@ -268,7 +291,7 @@ class DocumentService(CommonService):
@classmethod @classmethod
@DB.connection_context() @DB.connection_context()
def get_thumbnails(cls, docids): def get_thumbnails(cls, docids):
fields = [cls.model.id, cls.model.kb_id, cls.model.thumbnail] fields = [cls.model.id, cls.model.thumbnail]
return list(cls.model.select( return list(cls.model.select(
*fields).where(cls.model.id.in_(docids)).dicts()) *fields).where(cls.model.id.in_(docids)).dicts())

View File

@ -1441,60 +1441,196 @@ Create a chat session
### Request ### Request
- Method: POST - Method: POST
- URL: `/api/v1/chat/{chat_id}/session` - URL: `http://{address}/api/v1/chat/{chat_id}/session`
- Headers: - Headers:
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body:
- name: `string`
#### Request example #### Request example
```bash
curl --request POST \ curl --request POST \
--url http://{address}/api/v1/chat/{chat_id}/session \ --url http://{address}/api/v1/chat/{chat_id}/session \
--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 '{
"name": "new session" "name": "new session"
}' }'
```
#### Request parameters
- `"id"`: (*Body parameter*)
The ID of the created session used to identify different sessions.
- `None`
- `id` cannot be provided when creating.
- `"name"`: (*Body parameter*)
The name of the created session.
- `"New session"`
- `"messages"`: (*Body parameter*)
The messages of the created session.
- `[{"role": "assistant", "content": "Hi! I am your assistant, can I help you?"}]`
- `messages` cannot be provided when creating.
- `"chat_id"`: (*Path parameter*)
The ID of the associated chat.
- `""`
- `chat_id` cannot be changed.
### Response
Success
```json
{
"code": 0,
"data": {
"chat_id": "2ca4b22e878011ef88fe0242ac120005",
"create_date": "Fri, 11 Oct 2024 08:46:14 GMT",
"create_time": 1728636374571,
"id": "4606b4ec87ad11efbc4f0242ac120006",
"messages": [
{
"content": "Hi! I am your assistantcan I help you?",
"role": "assistant"
}
],
"name": "new session",
"update_date": "Fri, 11 Oct 2024 08:46:14 GMT",
"update_time": 1728636374571
}
}
```
Error
```json
{
"code": 102,
"message": "Name can not be empty."
}
```
## List the sessions of a chat ## List the sessions of a chat
**GET** `/api/v1/chat/{chat_id}/session` **GET** `/api/v1/chat/{chat_id}/session?page={page}&page_size={page_size}&orderby={orderby}&desc={desc}&name={dataset_name}&id={dataset_id}`
List all the session of a chat List all sessions under the chat based on the filtering criteria.
### Request ### Request
- Method: GET - Method: GET
- URL: `/api/v1/chat/{chat_id}/session` - URL: `http://{address}/api/v1/chat/{chat_id}/session?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/554e96746aaa11efb06b0242ac120005/session \ --url http://{address}/api/v1/chat/{chat_id}/session?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}'
```
## Delete a chat session #### 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`
**DELETE** `/api/v1/chat/{chat_id}/session/{session_id}` - `"page_size"`: (*Path parameter*)
The number of records to retrieve per page. This controls how many records will be included in each page.
- `1024`
Delete a chat session - `"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 session to be retrieved.
- `None`
- `"name"`: (*Path parameter*)
The name of the session to be retrieved.
- `None`
### Response
Success
```json
{
"code": 0,
"data": [
{
"chat": "2ca4b22e878011ef88fe0242ac120005",
"create_date": "Fri, 11 Oct 2024 08:46:43 GMT",
"create_time": 1728636403974,
"id": "578d541e87ad11ef96b90242ac120006",
"messages": [
{
"content": "Hi! I am your assistantcan I help you?",
"role": "assistant"
}
],
"name": "new session",
"update_date": "Fri, 11 Oct 2024 08:46:43 GMT",
"update_time": 1728636403974
}
]
}
```
Error
```json
{
"code": 102,
"message": "The session doesn't exist"
}
```
## Delete chat sessions
**DELETE** `/api/v1/chat/{chat_id}/session`
Delete chat sessions
### Request ### Request
- Method: DELETE - Method: DELETE
- URL: `/api/v1/chat/{chat_id}/session/{session_id}` - URL: `http://{address}/api/v1/chat/{chat_id}/session`
- 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
# Either id or name must be provided, but not both.
curl --request DELETE \ curl --request DELETE \
--url http://{address}/api/v1/chat/554e96746aaa11efb06b0242ac120005/session/791aed9670ea11efbb7e0242ac120007 \ --url http://{address}/api/v1/chat/{chat_id}/session \
--header 'Content-Type: application/json' \ --header 'Content-Type: application/json' \
--header 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' --header 'Authorization: Bear {YOUR_ACCESS_TOKEN}' \
--data '{
"ids": ["test_1", "test_2"]
}'
```
#### Request Parameters
- `ids`: (*Body Parameter*)
IDs of the sessions to be deleted.
- `None`
### Response
Success
```json
{
"code": 0
}
```
Error
```json
{
"code": 102,
"message": "The chat doesn't own the session"
}
```
## Update a chat session ## Update a chat session
**PUT** `/api/v1/chat/{chat_id}/session/{session_id}` **PUT** `/api/v1/chat/{chat_id}/session/{session_id}`
@ -1504,20 +1640,45 @@ Update a chat session
### Request ### Request
- Method: PUT - Method: PUT
- URL: `/api/v1/chat/{chat_id}/session/{session_id}` - URL: `http://{address}/api/v1/chat/{chat_id}/session/{session_id}`
- Headers: - Headers:
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body:
- `name`: string
#### Request example #### Request example
```bash
curl --request PUT \ curl --request PUT \
--url http://{address}/api/v1/chat/554e96746aaa11efb06b0242ac120005/session/791aed9670ea11efbb7e0242ac120007 \ --url http://{address}/api/v1/chat/{chat_id}/session/{session_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 '{
"name": "Updated session" "name": "Updated session"
}' }'
```
#### Request Parameter
- `name`:(*Body Parameter)
The name of the created session.
- `None`
### Response
Success
```json
{
"code": 0
}
```
Error
```json
{
"code": 102,
"message": "Name can not be empty."
}
```
## Chat with a chat session ## Chat with a chat session
**POST** `/api/v1/chat/{chat_id}/session/{session_id}/completion` **POST** `/api/v1/chat/{chat_id}/session/{session_id}/completion`
@ -1527,17 +1688,139 @@ Chat with a chat session
### Request ### Request
- Method: POST - Method: POST
- URL: `/api/v1/chat/{chat_id}/session/{session_id}/completion` - URL: `http://{address} /api/v1/chat/{chat_id}/session/{session_id}/completion`
- Headers: - Headers:
- `content-Type: application/json` - `content-Type: application/json`
- 'Authorization: Bearer {YOUR_ACCESS_TOKEN}' - 'Authorization: Bearer {YOUR_ACCESS_TOKEN}'
- Body:
- `question`: string
- `stream`: bool
#### Request example #### Request example
```bash
curl --request POST \ curl --request POST \
--url http://{address}/api/v1/chat/554e96746aaa11efb06b0242ac120005/session/791aed9670ea11efbb7e0242ac120007/completion \ --url http://{address} /api/v1/chat/{chat_id}/session/{session_id}/completion \
--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 '{
"question": "Hello!", "question": "你好!",
"stream": true, "stream": true
}' }'
```
#### Request Parameters
- `question`:(*Body Parameter*)
The question you want to ask.
- question is required.
`None`
- `stream`: (*Body Parameter*)
The approach of streaming text generation.
`False`
### Response
Success
```json
data: {
"code": 0,
"data": {
"answer": "您好!有什么具体的问题或者需要的帮助",
"reference": {},
"audio_binary": null,
"id": "31153052-7bac-4741-a513-ed07d853f29e"
}
}
data: {
"code": 0,
"data": {
"answer": "您好!有什么具体的问题或者需要的帮助可以告诉我吗?我在这里是为了帮助",
"reference": {},
"audio_binary": null,
"id": "31153052-7bac-4741-a513-ed07d853f29e"
}
}
data: {
"code": 0,
"data": {
"answer": "您好!有什么具体的问题或者需要的帮助可以告诉我吗?我在这里是为了帮助您的。如果您有任何疑问或是需要获取",
"reference": {},
"audio_binary": null,
"id": "31153052-7bac-4741-a513-ed07d853f29e"
}
}
data: {
"code": 0,
"data": {
"answer": "您好!有什么具体的问题或者需要的帮助可以告诉我吗?我在这里是为了帮助您的。如果您有任何疑问或是需要获取某些信息,请随时提出。",
"reference": {},
"audio_binary": null,
"id": "31153052-7bac-4741-a513-ed07d853f29e"
}
}
data: {
"code": 0,
"data": {
"answer": "您好!有什么具体的问题或者需要的帮助可以告诉我吗 ##0$$?我在这里是为了帮助您的。如果您有任何疑问或是需要获取某些信息,请随时提出。",
"reference": {
"total": 19,
"chunks": [
{
"chunk_id": "9d87f9d70a0d8a7565694a81fd4c5d5f",
"content_ltks": "当所有知识库内容都与问题无关时 ,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\r\n以下是知识库:\r\n{knowledg}\r\n以上是知识库\r\n\"\"\"\r\n 1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n总结\r\n通过上面的介绍,可以对开源的 ragflow有了一个大致的了解,与前面的有道qanyth整体流程还是比较类似的。 ",
"content_with_weight": "当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\r\n 以下是知识库:\r\n {knowledge}\r\n 以上是知识库\r\n\"\"\"\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n总结\r\n通过上面的介绍可以对开源的 RagFlow 有了一个大致的了解,与前面的 有道 QAnything 整体流程还是比较类似的。",
"doc_id": "5c5999ec7be811ef9cab0242ac120005",
"docnm_kwd": "1.txt",
"kb_id": "c7ee74067a2c11efb21c0242ac120006",
"important_kwd": [],
"img_id": "",
"similarity": 0.38337178633282265,
"vector_similarity": 0.3321336754679629,
"term_similarity": 0.4053309767034769,
"positions": [
""
]
},
{
"chunk_id": "895d34de762e674b43e8613c6fb54c6d",
"content_ltks": "\r\n\r\n实际内容可能会超过大模型的输入token数量,因此在调用大模型前会调用api/db/servic/dialog_service.py文件中 messag_fit_in ()根据大模型可用的 token数量进行过滤。这部分与有道的 qanyth的实现大同小异,就不额外展开了。\r\n\r\n将检索的内容,历史聊天记录以及问题构造为 prompt ,即可作为大模型的输入了 ,默认的英文prompt如下所示:\r\n\r\n\"\"\"\r\nyou are an intellig assistant. pleas summar the content of the knowledg base to answer the question. pleas list thedata in the knowledg base and answer in detail. when all knowledg base content is irrelev to the question , your answer must includ the sentenc\"the answer you are lookfor isnot found in the knowledg base!\" answer needto consid chat history.\r\n here is the knowledg base:\r\n{ knowledg}\r\nthe abov is the knowledg base.\r\n\"\"\"\r\n1\r\n 2\r\n 3\r\n 4\r\n 5\r\n 6\r\n对应的中文prompt如下所示:\r\n\r\n\"\"\"\r\n你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。 ",
"content_with_weight": "\r\n\r\n实际内容可能会超过大模型的输入 token 数量,因此在调用大模型前会调用 api/db/services/dialog_service.py 文件中 message_fit_in() 根据大模型可用的 token 数量进行过滤。这部分与有道的 QAnything 的实现大同小异,就不额外展开了。\r\n\r\n将检索的内容历史聊天记录以及问题构造为 prompt即可作为大模型的输入了默认的英文 prompt 如下所示:\r\n\r\n\"\"\"\r\nYou 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.\r\n Here is the knowledge base:\r\n {knowledge}\r\n The above is the knowledge base.\r\n\"\"\"\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n对应的中文 prompt 如下所示:\r\n\r\n\"\"\"\r\n你是一个智能助手请总结知识库的内容来回答问题请列举知识库中的数据详细回答。",
"doc_id": "5c5999ec7be811ef9cab0242ac120005",
"docnm_kwd": "1.txt",
"kb_id": "c7ee74067a2c11efb21c0242ac120006",
"important_kwd": [],
"img_id": "",
"similarity": 0.2788204323926715,
"vector_similarity": 0.35489427679953667,
"term_similarity": 0.2462173562183008,
"positions": [
""
]
}
],
"doc_aggs": [
{
"doc_name": "1.txt",
"doc_id": "5c5999ec7be811ef9cab0242ac120005",
"count": 2
}
]
},
"prompt": "你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\n 以下是知识库:\n 当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\r\n 以下是知识库:\r\n {knowledge}\r\n 以上是知识库\r\n\"\"\"\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n总结\r\n通过上面的介绍可以对开源的 RagFlow 有了一个大致的了解,与前面的 有道 QAnything 整体流程还是比较类似的。\n\n------\n\n\r\n\r\n实际内容可能会超过大模型的输入 token 数量,因此在调用大模型前会调用 api/db/services/dialog_service.py 文件中 message_fit_in() 根据大模型可用的 token 数量进行过滤。这部分与有道的 QAnything 的实现大同小异,就不额外展开了。\r\n\r\n将检索的内容历史聊天记录以及问题构造为 prompt即可作为大模型的输入了默认的英文 prompt 如下所示:\r\n\r\n\"\"\"\r\nYou 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.\r\n Here is the knowledge base:\r\n {knowledge}\r\n The above is the knowledge base.\r\n\"\"\"\r\n1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n对应的中文 prompt 如下所示:\r\n\r\n\"\"\"\r\n你是一个智能助手请总结知识库的内容来回答问题请列举知识库中的数据详细回答。\n 以上是知识库。\n\n### Query:\n你好请问有什么问题需要我帮忙解答吗\n\n### Elapsed\n - Retrieval: 9131.1 ms\n - LLM: 12802.6 ms",
"id": "31153052-7bac-4741-a513-ed07d853f29e"
}
}
data:{
"code": 0,
"data": true
}
```
Error
```json
{
"code": 102,
"message": "Please input your question."
}
```

View File

@ -906,7 +906,7 @@ Chat-session APIs
## Create session ## Create session
```python ```python
assistant_1.create_session(name: str = "New session") -> Session Chat.create_session(name: str = "New session") -> Session
``` ```
### Returns ### Returns
@ -916,8 +916,7 @@ A `session` object.
#### id: `str` #### id: `str`
The id of the created session is used to identify different sessions. The id of the created session is used to identify different sessions.
- `id` cannot be provided in creating - id can not be provided in creating
- `id` is required in updating
#### name: `str` #### name: `str`
@ -936,10 +935,10 @@ Defaults:
[{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}] [{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}]
``` ```
#### assistant_id: `str` #### chat_id: `str`
The id of associated assistant. Defaults to `""`. The id of associated chat
- `assistant_id` is required in creating if you use HTTP API. - `chat_id` can't be changed
### Examples ### Examples
@ -947,58 +946,21 @@ The id of associated assistant. Defaults to `""`.
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") assi = rag.list_chats(name="Miss R")
assi = assi[0]
sess = assi.create_session() sess = assi.create_session()
``` ```
## Retrieve session
## Update session
```python ```python
Assistant.get_session(id: str) -> Session Session.update(update_message:dict)
``` ```
### Parameters
#### id: `str`, *Required*
???????????????????????????????
### Returns ### Returns
### Returns no return
A `session` object.
#### id: `str`
The id of the created session is used to identify different sessions.
- `id` cannot be provided in creating
- `id` is required in updating
#### name: `str`
The name of the created session. Defaults to `"New session"`.
#### messages: `List[Message]`
The messages of the created session.
- messages cannot be provided.
Defaults:
??????????????????????????????????????????????????????????????????????????????????????????????
```
[{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}]
```
#### assistant_id: `str`
???????????????????????????????????????How to get
The id of associated assistant. Defaults to `""`.
- `assistant_id` is required in creating if you use HTTP API.
### Examples ### Examples
@ -1006,33 +968,10 @@ The id of associated assistant. Defaults to `""`.
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") assi = rag.list_chats(name="Miss R")
sess = assi.get_session(id="d5c55d2270dd11ef9bd90242ac120007") assi = assi[0]
``` sess = assi.create_session("new_session")
sess.update({"name": "Updated session"...})
---
## Save session settings
```python
Session.save() -> bool
```
### Returns
bool
description:the case of updating a session, True or False.
### Examples
```python
from ragflow import RAGFlow
rag = RAGFlow(api_key="xxxxxx", base_url="http://xxx.xx.xx.xxx:9380")
assi = rag.get_assistant(name="Miss R")
sess = assi.get_session(id="d5c55d2270dd11ef9bd90242ac120007")
sess.name = "Updated session"
sess.save()
``` ```
--- ---
@ -1040,7 +979,7 @@ sess.save()
## Chat ## Chat
```python ```python
Session.chat(question: str, stream: bool = False) -> Optional[Message, iter[Message]] Session.ask(question: str, stream: bool = False) -> Optional[Message, iter[Message]]
``` ```
### Parameters ### Parameters
@ -1053,7 +992,6 @@ The question to start an AI chat. Defaults to `None`. ???????????????????
The approach of streaming text generation. When stream is True, it outputs results in a streaming fashion; otherwise, it outputs the complete result after the model has finished generating. The approach of streaming text generation. When stream is True, it outputs results in a streaming fashion; otherwise, it outputs the complete result after the model has finished generating.
#### session_id: `str` ??????????????????
### Returns ### Returns
@ -1098,7 +1036,8 @@ The auto-generated reference of the message. Each `chunk` object includes the fo
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") assi = rag.list_chats(name="Miss R")
assi = assi[0]
sess = assi.create_session() sess = assi.create_session()
print("\n==================== Miss R =====================\n") print("\n==================== Miss R =====================\n")
@ -1109,9 +1048,10 @@ while True:
print("\n==================== Miss R =====================\n") print("\n==================== Miss R =====================\n")
cont = "" cont = ""
for ans in sess.chat(question, stream=True): for ans in sess.ask(question, stream=True):
print(ans.content[len(cont):], end='', flush=True) print(ans.content[len(cont):], end='', flush=True)
cont = ans.content cont = ans.content
``` ```
--- ---
@ -1119,7 +1059,14 @@ while True:
## List sessions ## List sessions
```python ```python
Assistant.list_session() -> List[Session] Chat.list_sessions(
page: int = 1,
page_size: int = 1024,
orderby: str = "create_time",
desc: bool = True,
id: str = None,
name: str = None
) -> List[Session]
``` ```
### Returns ### Returns
@ -1133,24 +1080,54 @@ description: the List contains information about multiple assistant object, with
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") assi = rag.list_chats(name="Miss R")
assi = assi[0]
for sess in assi.list_session(): for sess in assi.list_sessions():
print(sess) print(sess)
``` ```
### Parameters
#### page: `int`
The current page number to retrieve from the paginated data. This parameter determines which set of records will be fetched.
- `1`
#### page_size: `int`
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`
--- ---
## Delete session ## Delete session
```python ```python
Session.delete() -> bool Chat.delete_sessions(ids:List[str] = None)
``` ```
### Returns ### Returns
bool no return
description:the case of deleting a session, True or False.
### Examples ### Examples
@ -1158,7 +1135,12 @@ description:the case of deleting a session, 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")
assi = rag.get_assistant(name="Miss R") assi = rag.list_chats(name="Miss R")
sess = assi.create_session() assi = assi[0]
sess.delete() assi.delete_sessions(ids=["id_1","id_2"])
``` ```
### Parameters
#### ids: `List[string]`
IDs of the sessions to be deleted.
- `None`

View File

@ -51,28 +51,28 @@ class Chat(Base):
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(f"/chat/{self.id}/session", {"name": name})
res = res.json() res = res.json()
if res.get("retmsg") == "success": if res.get("code") == 0:
return Session(self.rag, res['data']) return Session(self.rag, res['data'])
raise Exception(res["retmsg"]) raise Exception(res["message"])
def list_session(self) -> List[Session]: def list_sessions(self,page: int = 1, page_size: int = 1024, orderby: str = "create_time", desc: bool = True,
res = self.get('/session/list', {"assistant_id": self.id}) id: str = None, name: str = None) -> List[Session]:
res = self.get(f'/chat/{self.id}/session',{"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id, "name": name} )
res = res.json() res = res.json()
if res.get("retmsg") == "success": if res.get("code") == 0:
result_list = [] result_list = []
for data in res["data"]: for data in res["data"]:
result_list.append(Session(self.rag, data)) result_list.append(Session(self.rag, data))
return result_list return result_list
raise Exception(res["retmsg"]) raise Exception(res["message"])
def get_session(self, id) -> Session: def delete_sessions(self,ids):
res = self.get("/session/get", {"id": id,"assistant_id":self.id}) res = self.rm(f"/chat/{self.id}/session", {"ids": ids})
res = res.json() res = res.json()
if res.get("retmsg") == "success": if res.get("code") != 0:
return Session(self.rag, res["data"]) raise Exception(res.get("message"))
raise Exception(res["retmsg"])
def get_prologue(self): def get_prologue(self):
return self.prompt.opener return self.prompt.opener

View File

@ -8,20 +8,20 @@ class Session(Base):
self.id = None self.id = None
self.name = "New session" self.name = "New session"
self.messages = [{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}] self.messages = [{"role": "assistant", "content": "Hi! I am your assistantcan I help you?"}]
self.assistant_id = None self.chat_id = None
super().__init__(rag, res_dict) super().__init__(rag, res_dict)
def chat(self, question: str, stream: bool = False): def ask(self, question: str, stream: bool = False):
for message in self.messages: for message in self.messages:
if "reference" in message: if "reference" in message:
message.pop("reference") message.pop("reference")
res = self.post("/session/completion", res = self.post(f"/chat/{self.chat_id}/session/{self.id}/completion",
{"session_id": self.id, "question": question, "stream": True}, stream=stream) {"question": question, "stream": True}, stream=stream)
for line in res.iter_lines(): for line in res.iter_lines():
line = line.decode("utf-8") line = line.decode("utf-8")
if line.startswith("{"): if line.startswith("{"):
json_data = json.loads(line) json_data = json.loads(line)
raise Exception(json_data["retmsg"]) raise Exception(json_data["message"])
if line.startswith("data:"): if line.startswith("data:"):
json_data = json.loads(line[5:]) json_data = json.loads(line[5:])
if json_data["data"] != True: if json_data["data"] != True:
@ -52,19 +52,12 @@ class Session(Base):
message = Message(self.rag, temp_dict) message = Message(self.rag, temp_dict)
yield message yield message
def save(self): def update(self,update_message):
res = self.post("/session/save", res = self.put(f"/chat/{self.chat_id}/session/{self.id}",
{"id": self.id, "assistant_id": self.assistant_id, "name": self.name}) update_message)
res = res.json() res = res.json()
if res.get("retmsg") == "success": return True if res.get("code") != 0:
raise Exception(res.get("retmsg")) raise Exception(res.get("message"))
def delete(self):
res = self.rm("/session/delete", {"id": self.id})
res = res.json()
if res.get("retmsg") == "success": return True
raise Exception(res.get("retmsg"))
class Message(Base): class Message(Base):
def __init__(self, rag, res_dict): def __init__(self, rag, res_dict):

View File

@ -7,52 +7,44 @@ class TestSession:
def test_create_session(self): def test_create_session(self):
rag = RAGFlow(API_KEY, HOST_ADDRESS) rag = RAGFlow(API_KEY, HOST_ADDRESS)
kb = rag.create_dataset(name="test_create_session") kb = rag.create_dataset(name="test_create_session")
assistant = rag.create_assistant(name="test_create_session", knowledgebases=[kb]) assistant = rag.create_chat(name="test_create_session", knowledgebases=[kb])
session = assistant.create_session() session = assistant.create_session()
assert isinstance(session,Session), "Failed to create a session." assert isinstance(session,Session), "Failed to create a session."
def test_create_chat_with_success(self): def test_create_chat_with_success(self):
rag = RAGFlow(API_KEY, HOST_ADDRESS) rag = RAGFlow(API_KEY, HOST_ADDRESS)
kb = rag.create_dataset(name="test_create_chat") kb = rag.create_dataset(name="test_create_chat")
assistant = rag.create_assistant(name="test_create_chat", knowledgebases=[kb]) assistant = rag.create_chat(name="test_create_chat", knowledgebases=[kb])
session = assistant.create_session() session = assistant.create_session()
question = "What is AI" question = "What is AI"
for ans in session.chat(question, stream=True): for ans in session.ask(question, stream=True):
pass pass
assert not ans.content.startswith("**ERROR**"), "Please check this error." assert not ans.content.startswith("**ERROR**"), "Please check this error."
def test_delete_session_with_success(self): def test_delete_sessions_with_success(self):
rag = RAGFlow(API_KEY, HOST_ADDRESS) rag = RAGFlow(API_KEY, HOST_ADDRESS)
kb = rag.create_dataset(name="test_delete_session") kb = rag.create_dataset(name="test_delete_session")
assistant = rag.create_assistant(name="test_delete_session",knowledgebases=[kb]) assistant = rag.create_chat(name="test_delete_session",knowledgebases=[kb])
session=assistant.create_session() session=assistant.create_session()
res=session.delete() res=assistant.delete_sessions(ids=[session.id])
assert res, "Failed to delete the dataset." assert res is None, "Failed to delete the dataset."
def test_update_session_with_success(self): def test_update_session_with_success(self):
rag=RAGFlow(API_KEY,HOST_ADDRESS) rag=RAGFlow(API_KEY,HOST_ADDRESS)
kb=rag.create_dataset(name="test_update_session") kb=rag.create_dataset(name="test_update_session")
assistant = rag.create_assistant(name="test_update_session",knowledgebases=[kb]) assistant = rag.create_chat(name="test_update_session",knowledgebases=[kb])
session=assistant.create_session(name="old session") session=assistant.create_session(name="old session")
session.name="new session" res=session.update({"name":"new session"})
res=session.save() assert res is None,"Failed to update the session"
assert res,"Failed to update the session"
def test_get_session_with_success(self):
rag=RAGFlow(API_KEY,HOST_ADDRESS)
kb=rag.create_dataset(name="test_get_session")
assistant = rag.create_assistant(name="test_get_session",knowledgebases=[kb])
session = assistant.create_session()
session_2= assistant.get_session(id=session.id)
assert session.to_json()==session_2.to_json(),"Failed to get the session"
def test_list_session_with_success(self): def test_list_sessions_with_success(self):
rag=RAGFlow(API_KEY,HOST_ADDRESS) rag=RAGFlow(API_KEY,HOST_ADDRESS)
kb=rag.create_dataset(name="test_list_session") kb=rag.create_dataset(name="test_list_session")
assistant=rag.create_assistant(name="test_list_session",knowledgebases=[kb]) assistant=rag.create_chat(name="test_list_session",knowledgebases=[kb])
assistant.create_session("test_1") assistant.create_session("test_1")
assistant.create_session("test_2") assistant.create_session("test_2")
sessions=assistant.list_session() sessions=assistant.list_sessions()
if isinstance(sessions,list): if isinstance(sessions,list):
for session in sessions: for session in sessions:
assert isinstance(session,Session),"Non-Session elements exist in the list" assert isinstance(session,Session),"Non-Session elements exist in the list"