2024-10-22 13:12:49 +08:00
#
# 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.
#
2024-09-14 13:24:21 +08:00
import datetime
2025-12-11 09:59:15 +08:00
import json
2025-06-05 12:46:29 +08:00
import logging
import pathlib
2024-09-14 13:24:21 +08:00
import re
from io import BytesIO
2025-06-05 12:46:29 +08:00
import xxhash
2025-11-18 17:05:16 +08:00
from quart import request , send_file
2025-06-05 12:46:29 +08:00
from peewee import OperationalError
from pydantic import BaseModel , Field , validator
2025-06-17 18:01:30 +08:00
from api . constants import FILE_NAME_LEN_LIMIT
2025-11-05 08:01:39 +08:00
from api . db import FileType
2025-06-05 12:46:29 +08:00
from api . db . db_models import File , Task
2024-09-12 14:19:45 +08:00
from api . db . services . document_service import DocumentService
from api . db . services . file2document_service import File2DocumentService
from api . db . services . file_service import FileService
from api . db . services . knowledgebase_service import KnowledgebaseService
2025-08-13 16:41:01 +08:00
from api . db . services . llm_service import LLMBundle
from api . db . services . tenant_llm_service import TenantLLMService
2025-12-04 19:29:06 +08:00
from api . db . services . task_service import TaskService , queue_tasks , cancel_all_task_of
2025-12-12 17:12:38 +08:00
from common . metadata_utils import meta_filter , convert_conditions
2025-11-18 17:05:16 +08:00
from api . utils . api_utils import check_duplicate_ids , construct_json_result , get_error_data_result , get_parser_config , get_result , server_error_response , token_required , \
2025-12-01 14:24:06 +08:00
get_request_json
2025-06-05 12:46:29 +08:00
from rag . app . qa import beAdoc , rmPrefix
2025-02-26 15:40:52 +08:00
from rag . app . tag import label_question
2025-06-05 12:46:29 +08:00
from rag . nlp import rag_tokenizer , search
2025-09-23 10:19:25 +08:00
from rag . prompts . generator import cross_languages , keyword_extraction
2025-10-28 09:46:32 +08:00
from common . string_utils import remove_redundant_spaces
2025-11-05 08:01:39 +08:00
from common . constants import RetCode , LLMType , ParserType , TaskStatus , FileSource
2025-11-06 09:36:38 +08:00
from common import settings
2024-09-12 14:19:45 +08:00
2024-10-30 16:15:42 +08:00
MAXIMUM_OF_UPLOADING_FILES = 256
2024-12-20 22:55:45 +08:00
class Chunk ( BaseModel ) :
id : str = " "
content : str = " "
document_id : str = " "
docnm_kwd : str = " "
important_keywords : list = Field ( default_factory = list )
questions : list = Field ( default_factory = list )
question_tks : str = " "
image_id : str = " "
available : bool = True
positions : list [ list [ int ] ] = Field ( default_factory = list )
2025-06-05 12:46:29 +08:00
@validator ( " positions " )
2024-12-20 22:55:45 +08:00
def validate_positions ( cls , value ) :
for sublist in value :
if len ( sublist ) != 5 :
raise ValueError ( " Each sublist in positions must have a length of 5 " )
return value
2025-03-17 12:22:49 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/documents " , methods = [ " POST " ] ) # noqa: F821
2024-09-12 14:19:45 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def upload ( dataset_id , tenant_id ) :
2024-11-04 08:35:36 +01:00
"""
Upload documents to a dataset.
---
tags:
- Documents
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
- in: formData
name: file
type: file
required: true
description: Document files to upload.
2025-11-13 09:59:39 +08:00
- in: formData
name: parent_path
type: string
description: Optional nested path under the parent folder. Uses ' / ' separators.
2024-11-04 08:35:36 +01:00
responses:
200:
description: Successfully uploaded documents.
schema:
type: object
properties:
data:
type: array
items:
type: object
properties:
id:
type: string
description: Document ID.
name:
type: string
description: Document name.
chunk_count:
type: integer
description: Number of chunks.
token_count:
type: integer
description: Number of tokens.
dataset_id:
type: string
description: ID of the dataset.
chunk_method:
type: string
description: Chunking method used.
run:
type: string
description: Processing status.
"""
2025-11-18 17:05:16 +08:00
form = await request . form
files = await request . files
if " file " not in files :
2025-11-04 15:12:53 +08:00
return get_error_data_result ( message = " No file part! " , code = RetCode . ARGUMENT_ERROR )
2025-11-18 17:05:16 +08:00
file_objs = files . getlist ( " file " )
2024-09-12 14:19:45 +08:00
for file_obj in file_objs :
2024-11-04 08:35:36 +01:00
if file_obj . filename == " " :
2025-11-04 15:12:53 +08:00
return get_result ( message = " No file selected! " , code = RetCode . ARGUMENT_ERROR )
2025-06-17 18:01:30 +08:00
if len ( file_obj . filename . encode ( " utf-8 " ) ) > FILE_NAME_LEN_LIMIT :
2025-11-04 15:12:53 +08:00
return get_result ( message = f " File name must be { FILE_NAME_LEN_LIMIT } bytes or less. " , code = RetCode . ARGUMENT_ERROR )
2025-06-05 12:46:29 +08:00
"""
2024-10-30 16:15:42 +08:00
# total size
total_size = 0
for file_obj in file_objs:
file_obj.seek(0, os.SEEK_END)
total_size += file_obj.tell()
file_obj.seek(0)
2024-11-04 08:35:36 +01:00
MAX_TOTAL_FILE_SIZE = 10 * 1024 * 1024
2024-10-30 16:15:42 +08:00
if total_size > MAX_TOTAL_FILE_SIZE:
return get_result(
2024-11-05 11:02:31 +08:00
message=f " Total file size exceeds 10MB limit! ( { total_size / (1024 * 1024):.2f} MB) " ,
2025-11-04 15:12:53 +08:00
code=RetCode.ARGUMENT_ERROR,
2024-11-04 08:35:36 +01:00
)
2025-06-05 12:46:29 +08:00
"""
2024-09-12 14:19:45 +08:00
e , kb = KnowledgebaseService . get_by_id ( dataset_id )
if not e :
2024-10-23 12:02:18 +08:00
raise LookupError ( f " Can ' t find the dataset with ID { dataset_id } ! " )
2025-11-18 17:05:16 +08:00
err , files = FileService . upload_document ( kb , file_objs , tenant_id , parent_path = form . get ( " parent_path " ) )
2024-09-12 14:19:45 +08:00
if err :
2025-11-04 15:12:53 +08:00
return get_result ( message = " \n " . join ( err ) , code = RetCode . SERVER_ERROR )
2024-10-23 12:02:18 +08:00
# rename key's name
renamed_doc_list = [ ]
for file in files :
doc = file [ 0 ]
key_mapping = {
" chunk_num " : " chunk_count " ,
" kb_id " : " dataset_id " ,
" token_num " : " token_count " ,
2024-11-04 08:35:36 +01:00
" parser_id " : " chunk_method " ,
2024-10-23 12:02:18 +08:00
}
renamed_doc = { }
for key , value in doc . items ( ) :
new_key = key_mapping . get ( key , key )
renamed_doc [ new_key ] = value
renamed_doc [ " run " ] = " UNSTART "
renamed_doc_list . append ( renamed_doc )
return get_result ( data = renamed_doc_list )
2024-09-12 14:19:45 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/documents/<document_id> " , methods = [ " PUT " ] ) # noqa: F821
2024-09-12 14:19:45 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def update_doc ( tenant_id , dataset_id , document_id ) :
2024-11-04 08:35:36 +01:00
"""
Update a document within a dataset.
---
tags:
- Documents
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: path
name: document_id
type: string
required: true
description: ID of the document to update.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
- in: body
name: body
description: Document update parameters.
required: true
schema:
type: object
properties:
name:
type: string
description: New name of the document.
parser_config:
type: object
description: Parser configuration.
chunk_method:
type: string
description: Chunking method.
2025-05-09 12:20:07 +08:00
enabled:
type: boolean
description: Document status.
2024-11-04 08:35:36 +01:00
responses:
200:
description: Document updated successfully.
schema:
type: object
"""
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2024-10-12 19:35:19 +08:00
if not KnowledgebaseService . query ( id = dataset_id , tenant_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " You don ' t own the dataset. " )
2025-05-09 12:20:07 +08:00
e , kb = KnowledgebaseService . get_by_id ( dataset_id )
if not e :
2025-12-17 10:03:33 +08:00
return get_error_data_result ( message = " Can ' t find this dataset! " )
2024-10-12 19:35:19 +08:00
doc = DocumentService . query ( kb_id = dataset_id , id = document_id )
if not doc :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " The dataset doesn ' t own the document. " )
2024-10-12 19:35:19 +08:00
doc = doc [ 0 ]
2024-09-18 18:46:37 +08:00
if " chunk_count " in req :
if req [ " chunk_count " ] != doc . chunk_num :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Can ' t change `chunk_count`. " )
2024-09-18 18:46:37 +08:00
if " token_count " in req :
if req [ " token_count " ] != doc . token_num :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Can ' t change `token_count`. " )
2024-09-14 13:24:21 +08:00
if " progress " in req :
2024-11-04 08:35:36 +01:00
if req [ " progress " ] != doc . progress :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Can ' t change `progress`. " )
2024-09-14 13:24:21 +08:00
2025-03-07 16:21:27 +08:00
if " meta_fields " in req :
if not isinstance ( req [ " meta_fields " ] , dict ) :
return get_error_data_result ( message = " meta_fields must be a dictionary " )
DocumentService . update_meta_fields ( document_id , req [ " meta_fields " ] )
2024-10-12 19:35:19 +08:00
if " name " in req and req [ " name " ] != doc . name :
2025-06-17 18:01:30 +08:00
if len ( req [ " name " ] . encode ( " utf-8 " ) ) > FILE_NAME_LEN_LIMIT :
2025-03-14 15:01:37 +08:00
return get_result (
2025-06-17 18:01:30 +08:00
message = f " File name must be { FILE_NAME_LEN_LIMIT } bytes or less. " ,
2025-11-04 15:12:53 +08:00
code = RetCode . ARGUMENT_ERROR ,
2025-03-14 15:01:37 +08:00
)
2025-06-05 12:46:29 +08:00
if pathlib . Path ( req [ " name " ] . lower ( ) ) . suffix != pathlib . Path ( doc . name . lower ( ) ) . suffix :
2024-11-04 08:35:36 +01:00
return get_result (
2024-11-05 11:02:31 +08:00
message = " The extension of file can ' t be changed " ,
2025-11-04 15:12:53 +08:00
code = RetCode . ARGUMENT_ERROR ,
2024-11-04 08:35:36 +01:00
)
2024-10-12 19:35:19 +08:00
for d in DocumentService . query ( name = req [ " name " ] , kb_id = doc . kb_id ) :
if d . name == req [ " name " ] :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = " Duplicated document name in the same dataset. " )
2024-11-04 08:35:36 +01:00
if not DocumentService . update_by_id ( document_id , { " name " : req [ " name " ] } ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Database error (Document rename)! " )
2024-09-14 13:24:21 +08:00
2024-10-12 19:35:19 +08:00
informs = File2DocumentService . get_by_document_id ( document_id )
if informs :
e , file = FileService . get_by_id ( informs [ 0 ] . file_id )
FileService . update_by_id ( file . id , { " name " : req [ " name " ] } )
2025-03-07 16:21:27 +08:00
2024-10-16 18:41:24 +08:00
if " parser_config " in req :
DocumentService . update_parser_config ( doc . id , req [ " parser_config " ] )
2024-10-21 14:29:06 +08:00
if " chunk_method " in req :
2025-06-05 12:46:29 +08:00
valid_chunk_method = { " naive " , " manual " , " qa " , " table " , " paper " , " book " , " laws " , " presentation " , " picture " , " one " , " knowledge_graph " , " email " , " tag " }
2024-10-23 12:02:18 +08:00
if req . get ( " chunk_method " ) not in valid_chunk_method :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( f " `chunk_method` { req [ ' chunk_method ' ] } doesn ' t exist " )
2024-09-14 13:24:21 +08:00
2024-11-04 08:35:36 +01:00
if doc . type == FileType . VISUAL or re . search ( r " \ .(ppt|pptx|pages)$ " , doc . name ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Not supported yet! " )
2024-09-14 13:24:21 +08:00
2025-05-28 19:20:27 +08:00
if doc . parser_id . lower ( ) != req [ " chunk_method " ] . lower ( ) :
e = DocumentService . update_by_id (
doc . id ,
{
" parser_id " : req [ " chunk_method " ] ,
" progress " : 0 ,
" progress_msg " : " " ,
" run " : TaskStatus . UNSTART . value ,
} ,
)
if not e :
return get_error_data_result ( message = " Document not found! " )
if not req . get ( " parser_config " ) :
2025-06-05 12:46:29 +08:00
req [ " parser_config " ] = get_parser_config ( req [ " chunk_method " ] , req . get ( " parser_config " ) )
2025-05-28 19:20:27 +08:00
DocumentService . update_parser_config ( doc . id , req [ " parser_config " ] )
2024-09-14 13:24:21 +08:00
if doc . token_num > 0 :
2024-11-04 08:35:36 +01:00
e = DocumentService . increment_chunk_num (
doc . id ,
doc . kb_id ,
doc . token_num * - 1 ,
doc . chunk_num * - 1 ,
2025-07-07 14:11:47 +08:00
doc . process_duration * - 1 ,
2024-11-04 08:35:36 +01:00
)
2024-09-14 13:24:21 +08:00
if not e :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Document not found! " )
2025-11-06 09:36:38 +08:00
settings . docStoreConn . delete ( { " doc_id " : doc . id } , search . index_name ( tenant_id ) , dataset_id )
2024-09-14 13:24:21 +08:00
2025-05-09 12:20:07 +08:00
if " enabled " in req :
status = int ( req [ " enabled " ] )
if doc . status != req [ " enabled " ] :
try :
2025-06-05 12:46:29 +08:00
if not DocumentService . update_by_id ( doc . id , { " status " : str ( status ) } ) :
return get_error_data_result ( message = " Database error (Document update)! " )
2025-11-06 09:36:38 +08:00
settings . docStoreConn . update ( { " doc_id " : doc . id } , { " available_int " : status } , search . index_name ( kb . tenant_id ) , doc . kb_id )
2025-05-09 12:20:07 +08:00
except Exception as e :
return server_error_response ( e )
2025-06-05 12:46:29 +08:00
try :
ok , doc = DocumentService . get_by_id ( doc . id )
if not ok :
return get_error_data_result ( message = " Dataset created failed " )
except OperationalError as e :
logging . exception ( e )
return get_error_data_result ( message = " Database operation failed " )
key_mapping = {
" chunk_num " : " chunk_count " ,
" kb_id " : " dataset_id " ,
" token_num " : " token_count " ,
" parser_id " : " chunk_method " ,
}
run_mapping = {
" 0 " : " UNSTART " ,
" 1 " : " RUNNING " ,
" 2 " : " CANCEL " ,
" 3 " : " DONE " ,
" 4 " : " FAIL " ,
}
renamed_doc = { }
for key , value in doc . to_dict ( ) . items ( ) :
new_key = key_mapping . get ( key , key )
renamed_doc [ new_key ] = value
if key == " run " :
2025-12-04 11:24:01 +08:00
renamed_doc [ " run " ] = run_mapping . get ( str ( value ) )
2024-09-12 14:19:45 +08:00
2025-06-05 12:46:29 +08:00
return get_result ( data = renamed_doc )
2024-09-12 14:19:45 +08:00
2025-05-09 12:20:07 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/documents/<document_id> " , methods = [ " GET " ] ) # noqa: F821
2024-09-12 14:19:45 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def download ( tenant_id , dataset_id , document_id ) :
2024-11-04 08:35:36 +01:00
"""
Download a document from a dataset.
---
tags:
- Documents
security:
- ApiKeyAuth: []
produces:
- application/octet-stream
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: path
name: document_id
type: string
required: true
description: ID of the document to download.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Document file stream.
schema:
type: file
400:
description: Error message.
schema:
type: object
"""
2025-03-14 11:45:44 +08:00
if not document_id :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = " Specify document_id please. " )
2024-10-12 19:35:19 +08:00
if not KnowledgebaseService . query ( id = dataset_id , tenant_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You do not own the dataset { dataset_id } . " )
2024-10-12 19:35:19 +08:00
doc = DocumentService . query ( kb_id = dataset_id , id = document_id )
if not doc :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = f " The dataset not own the document { document_id } . " )
2024-10-12 19:35:19 +08:00
# The process of downloading
2025-06-05 12:46:29 +08:00
doc_id , doc_location = File2DocumentService . get_storage_address ( doc_id = document_id ) # minio address
2025-11-06 09:36:38 +08:00
file_stream = settings . STORAGE_IMPL . get ( doc_id , doc_location )
2024-10-12 19:35:19 +08:00
if not file_stream :
2025-11-04 15:12:53 +08:00
return construct_json_result ( message = " This file is empty. " , code = RetCode . DATA_ERROR )
2024-10-12 19:35:19 +08:00
file = BytesIO ( file_stream )
# Use send_file with a proper filename and MIME type
2025-11-18 17:05:16 +08:00
return await send_file (
2024-10-12 19:35:19 +08:00
file ,
as_attachment = True ,
2025-11-18 17:05:16 +08:00
attachment_filename = doc [ 0 ] . name ,
2024-11-04 08:35:36 +01:00
mimetype = " application/octet-stream " , # Set a default MIME type
2024-10-12 19:35:19 +08:00
)
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/documents " , methods = [ " GET " ] ) # noqa: F821
2024-09-12 14:19:45 +08:00
@token_required
2024-09-14 13:24:21 +08:00
def list_docs ( dataset_id , tenant_id ) :
2024-11-04 08:35:36 +01:00
"""
List documents in a dataset.
---
tags:
- Documents
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: query
name: id
type: string
required: false
description: Filter by document ID.
- in: query
2024-11-05 14:07:31 +08:00
name: page
2024-11-04 08:35:36 +01:00
type: integer
required: false
default: 1
description: Page number.
- in: query
2024-11-05 14:07:31 +08:00
name: page_size
2024-11-04 08:35:36 +01:00
type: integer
required: false
2024-11-05 14:07:31 +08:00
default: 30
2024-11-04 08:35:36 +01:00
description: Number of items per page.
- in: query
name: orderby
type: string
required: false
default: " create_time "
description: Field to order by.
- in: query
name: desc
type: boolean
required: false
default: true
description: Order in descending.
2025-10-10 18:36:20 +08:00
- in: query
2025-08-04 16:35:35 +08:00
name: create_time_from
type: integer
required: false
default: 0
description: Unix timestamp for filtering documents created after this time. 0 means no filter.
- in: query
name: create_time_to
type: integer
required: false
default: 0
description: Unix timestamp for filtering documents created before this time. 0 means no filter.
2025-10-21 10:38:40 +08:00
- in: query
name: suffix
type: array
items:
type: string
required: false
description: Filter by file suffix (e.g., [ " pdf " , " txt " , " docx " ]).
- in: query
name: run
type: array
items:
type: string
required: false
description: Filter by document run status. Supports both numeric ( " 0 " , " 1 " , " 2 " , " 3 " , " 4 " ) and text formats ( " UNSTART " , " RUNNING " , " CANCEL " , " DONE " , " FAIL " ).
2024-11-04 08:35:36 +01:00
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: List of documents.
schema:
type: object
properties:
total:
type: integer
description: Total number of documents.
docs:
type: array
items:
type: object
properties:
id:
type: string
description: Document ID.
name:
type: string
description: Document name.
chunk_count:
type: integer
description: Number of chunks.
token_count:
type: integer
description: Number of tokens.
dataset_id:
type: string
description: ID of the dataset.
chunk_method:
type: string
description: Chunking method used.
run:
type: string
description: Processing status.
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2025-10-21 10:38:40 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
q = request . args
2025-12-01 14:24:06 +08:00
document_id = q . get ( " id " )
2025-10-21 10:38:40 +08:00
name = q . get ( " name " )
2025-02-27 10:39:34 +08:00
2025-10-21 10:38:40 +08:00
if document_id and not DocumentService . query ( id = document_id , kb_id = dataset_id ) :
return get_error_data_result ( message = f " You don ' t own the document { document_id } . " )
2025-02-27 10:39:34 +08:00
if name and not DocumentService . query ( name = name , kb_id = dataset_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the document { name } . " )
2025-02-27 12:01:46 +08:00
2025-10-21 10:38:40 +08:00
page = int ( q . get ( " page " , 1 ) )
2025-12-01 14:24:06 +08:00
page_size = int ( q . get ( " page_size " , 30 ) )
2025-10-21 10:38:40 +08:00
orderby = q . get ( " orderby " , " create_time " )
desc = str ( q . get ( " desc " , " true " ) ) . strip ( ) . lower ( ) != " false "
keywords = q . get ( " keywords " , " " )
# filters - align with OpenAPI parameter names
2025-12-01 14:24:06 +08:00
suffix = q . getlist ( " suffix " )
run_status = q . getlist ( " run " )
create_time_from = int ( q . get ( " create_time_from " , 0 ) )
create_time_to = int ( q . get ( " create_time_to " , 0 ) )
2025-12-11 09:59:15 +08:00
metadata_condition_raw = q . get ( " metadata_condition " )
metadata_condition = { }
if metadata_condition_raw :
try :
metadata_condition = json . loads ( metadata_condition_raw )
except Exception :
return get_error_data_result ( message = " metadata_condition must be valid JSON. " )
if metadata_condition and not isinstance ( metadata_condition , dict ) :
return get_error_data_result ( message = " metadata_condition must be an object. " )
2024-09-18 11:08:19 +08:00
2025-12-08 12:21:18 +08:00
# map run status (text or numeric) - align with API parameter
2025-10-21 10:38:40 +08:00
run_status_text_to_numeric = { " UNSTART " : " 0 " , " RUNNING " : " 1 " , " CANCEL " : " 2 " , " DONE " : " 3 " , " FAIL " : " 4 " }
run_status_converted = [ run_status_text_to_numeric . get ( v , v ) for v in run_status ]
2025-08-04 16:35:35 +08:00
2025-12-11 09:59:15 +08:00
doc_ids_filter = None
if metadata_condition :
metas = DocumentService . get_flatted_meta_by_kbs ( [ dataset_id ] )
doc_ids_filter = meta_filter ( metas , convert_conditions ( metadata_condition ) , metadata_condition . get ( " logic " , " and " ) )
if metadata_condition . get ( " conditions " ) and not doc_ids_filter :
return get_result ( data = { " total " : 0 , " docs " : [ ] } )
2025-10-21 10:38:40 +08:00
docs , total = DocumentService . get_list (
2025-12-11 09:59:15 +08:00
dataset_id , page , page_size , orderby , desc , keywords , document_id , name , suffix , run_status_converted , doc_ids_filter
2025-10-21 10:38:40 +08:00
)
# time range filter (0 means no bound)
2025-08-04 16:35:35 +08:00
if create_time_from or create_time_to :
2025-10-21 10:38:40 +08:00
docs = [
d for d in docs
if ( create_time_from == 0 or d . get ( " create_time " , 0 ) > = create_time_from )
and ( create_time_to == 0 or d . get ( " create_time " , 0 ) < = create_time_to )
]
2025-08-04 16:35:35 +08:00
2025-10-21 10:38:40 +08:00
# rename keys + map run status back to text for output
2025-06-27 10:23:08 +08:00
key_mapping = {
" chunk_num " : " chunk_count " ,
2025-12-01 14:24:06 +08:00
" kb_id " : " dataset_id " ,
2025-06-27 10:23:08 +08:00
" token_num " : " token_count " ,
" parser_id " : " chunk_method " ,
}
2025-10-21 10:38:40 +08:00
run_status_numeric_to_text = { " 0 " : " UNSTART " , " 1 " : " RUNNING " , " 2 " : " CANCEL " , " 3 " : " DONE " , " 4 " : " FAIL " }
output_docs = [ ]
for d in docs :
renamed_doc = { key_mapping . get ( k , k ) : v for k , v in d . items ( ) }
if " run " in d :
renamed_doc [ " run " ] = run_status_numeric_to_text . get ( str ( d [ " run " ] ) , d [ " run " ] )
output_docs . append ( renamed_doc )
2024-09-12 14:19:45 +08:00
2025-10-21 10:38:40 +08:00
return get_result ( data = { " total " : total , " docs " : output_docs } )
2024-09-12 14:19:45 +08:00
2025-12-11 09:59:15 +08:00
@manager.route ( " /datasets/<dataset_id>/metadata/summary " , methods = [ " GET " ] ) # noqa: F821
@token_required
def metadata_summary ( dataset_id , tenant_id ) :
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
try :
summary = DocumentService . get_metadata_summary ( dataset_id )
return get_result ( data = { " summary " : summary } )
except Exception as e :
return server_error_response ( e )
@manager.route ( " /datasets/<dataset_id>/metadata/update " , methods = [ " POST " ] ) # noqa: F821
@token_required
async def metadata_batch_update ( dataset_id , tenant_id ) :
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
req = await get_request_json ( )
selector = req . get ( " selector " , { } ) or { }
updates = req . get ( " updates " , [ ] ) or [ ]
deletes = req . get ( " deletes " , [ ] ) or [ ]
if not isinstance ( selector , dict ) :
return get_error_data_result ( message = " selector must be an object. " )
if not isinstance ( updates , list ) or not isinstance ( deletes , list ) :
return get_error_data_result ( message = " updates and deletes must be lists. " )
metadata_condition = selector . get ( " metadata_condition " , { } ) or { }
if metadata_condition and not isinstance ( metadata_condition , dict ) :
return get_error_data_result ( message = " metadata_condition must be an object. " )
document_ids = selector . get ( " document_ids " , [ ] ) or [ ]
if document_ids and not isinstance ( document_ids , list ) :
return get_error_data_result ( message = " document_ids must be a list. " )
for upd in updates :
if not isinstance ( upd , dict ) or not upd . get ( " key " ) or " value " not in upd :
return get_error_data_result ( message = " Each update requires key and value. " )
for d in deletes :
if not isinstance ( d , dict ) or not d . get ( " key " ) :
return get_error_data_result ( message = " Each delete requires key. " )
2026-01-08 13:22:58 +08:00
2025-12-11 09:59:15 +08:00
if document_ids :
2026-01-08 13:22:58 +08:00
kb_doc_ids = KnowledgebaseService . list_documents_by_ids ( [ dataset_id ] )
target_doc_ids = set ( kb_doc_ids )
2025-12-11 09:59:15 +08:00
invalid_ids = set ( document_ids ) - set ( kb_doc_ids )
if invalid_ids :
return get_error_data_result ( message = f " These documents do not belong to dataset { dataset_id } : { ' , ' . join ( invalid_ids ) } " )
target_doc_ids = set ( document_ids )
if metadata_condition :
metas = DocumentService . get_flatted_meta_by_kbs ( [ dataset_id ] )
filtered_ids = set ( meta_filter ( metas , convert_conditions ( metadata_condition ) , metadata_condition . get ( " logic " , " and " ) ) )
target_doc_ids = target_doc_ids & filtered_ids
if metadata_condition . get ( " conditions " ) and not target_doc_ids :
return get_result ( data = { " updated " : 0 , " matched_docs " : 0 } )
target_doc_ids = list ( target_doc_ids )
updated = DocumentService . batch_update_metadata ( dataset_id , target_doc_ids , updates , deletes )
return get_result ( data = { " updated " : updated , " matched_docs " : len ( target_doc_ids ) } )
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/documents " , methods = [ " DELETE " ] ) # noqa: F821
2024-09-12 14:19:45 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def delete ( tenant_id , dataset_id ) :
2024-11-04 08:35:36 +01:00
"""
Delete documents from a dataset.
---
tags:
- Documents
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: body
name: body
description: Document deletion parameters.
required: true
schema:
type: object
properties:
ids:
type: array
items:
type: string
description: List of document IDs to delete.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Documents deleted successfully.
schema:
type: object
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2024-10-24 20:10:47 +08:00
if not req :
2024-11-04 08:35:36 +01:00
doc_ids = None
2024-10-24 20:10:47 +08:00
else :
2025-03-21 14:05:17 +08:00
doc_ids = req . get ( " ids " )
2024-10-24 20:10:47 +08:00
if not doc_ids :
doc_list = [ ]
2024-11-04 08:35:36 +01:00
docs = DocumentService . query ( kb_id = dataset_id )
2024-10-24 20:10:47 +08:00
for doc in docs :
doc_list . append ( doc . id )
else :
2024-11-04 08:35:36 +01:00
doc_list = doc_ids
2025-03-21 14:05:17 +08:00
unique_doc_ids , duplicate_messages = check_duplicate_ids ( doc_list , " document " )
doc_list = unique_doc_ids
2024-09-12 14:19:45 +08:00
root_folder = FileService . get_root_folder ( tenant_id )
pf_id = root_folder [ " id " ]
FileService . init_knowledgebase_docs ( pf_id , tenant_id )
errors = " "
2025-03-18 13:37:34 +08:00
not_found = [ ]
2025-03-21 14:05:17 +08:00
success_count = 0
2024-10-24 20:10:47 +08:00
for doc_id in doc_list :
2024-09-12 14:19:45 +08:00
try :
e , doc = DocumentService . get_by_id ( doc_id )
if not e :
2025-03-18 13:37:34 +08:00
not_found . append ( doc_id )
2025-03-18 10:44:50 +08:00
continue
2024-09-12 14:19:45 +08:00
tenant_id = DocumentService . get_tenant_id ( doc_id )
if not tenant_id :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Tenant not found! " )
2024-09-12 14:19:45 +08:00
2024-09-19 19:19:27 +08:00
b , n = File2DocumentService . get_storage_address ( doc_id = doc_id )
2024-09-12 14:19:45 +08:00
if not DocumentService . remove_document ( doc , tenant_id ) :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = " Database error (Document removal)! " )
2024-09-12 14:19:45 +08:00
f2d = File2DocumentService . get_by_document_id ( doc_id )
2024-11-04 08:35:36 +01:00
FileService . filter_delete (
[
File . source_type == FileSource . KNOWLEDGEBASE ,
File . id == f2d [ 0 ] . file_id ,
]
)
2024-09-12 14:19:45 +08:00
File2DocumentService . delete_by_document_id ( doc_id )
2025-11-06 09:36:38 +08:00
settings . STORAGE_IMPL . rm ( b , n )
2025-03-21 14:05:17 +08:00
success_count + = 1
2024-09-12 14:19:45 +08:00
except Exception as e :
errors + = str ( e )
2025-03-18 13:37:34 +08:00
if not_found :
2025-11-04 15:12:53 +08:00
return get_result ( message = f " Documents not found: { not_found } " , code = RetCode . DATA_ERROR )
2025-03-18 14:02:57 +08:00
2024-09-12 14:19:45 +08:00
if errors :
2025-11-04 15:12:53 +08:00
return get_result ( message = errors , code = RetCode . SERVER_ERROR )
2024-09-12 14:19:45 +08:00
2025-03-21 14:05:17 +08:00
if duplicate_messages :
if success_count > 0 :
2025-06-05 12:46:29 +08:00
return get_result (
message = f " Partially deleted { success_count } datasets with { len ( duplicate_messages ) } errors " ,
data = { " success_count " : success_count , " errors " : duplicate_messages } ,
)
2025-03-21 14:05:17 +08:00
else :
return get_error_data_result ( message = " ; " . join ( duplicate_messages ) )
2024-10-12 19:35:19 +08:00
return get_result ( )
2024-09-14 13:24:21 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/chunks " , methods = [ " POST " ] ) # noqa: F821
2024-09-14 13:24:21 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def parse ( tenant_id , dataset_id ) :
2024-11-04 08:35:36 +01:00
"""
Start parsing documents into chunks.
---
tags:
- Chunks
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: body
name: body
description: Parsing parameters.
required: true
schema:
type: object
properties:
document_ids:
type: array
items:
type: string
description: List of document IDs to parse.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Parsing started successfully.
schema:
type: object
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2024-10-16 18:41:24 +08:00
if not req . get ( " document_ids " ) :
return get_error_data_result ( " `document_ids` is required " )
2025-03-21 14:05:17 +08:00
doc_list = req . get ( " document_ids " )
unique_doc_ids , duplicate_messages = check_duplicate_ids ( doc_list , " document " )
doc_list = unique_doc_ids
2025-03-19 12:18:19 +08:00
not_found = [ ]
2025-03-21 14:05:17 +08:00
success_count = 0
for id in doc_list :
2024-11-04 08:35:36 +01:00
doc = DocumentService . query ( id = id , kb_id = dataset_id )
2025-03-19 12:18:19 +08:00
if not doc :
not_found . append ( id )
continue
2024-10-24 20:10:47 +08:00
if not doc :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the document { id } . " )
2025-03-20 16:00:17 +08:00
if 0.0 < doc [ 0 ] . progress < 1.0 :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( " Can ' t parse document that is currently being processed " )
2025-03-19 12:18:19 +08:00
info = { " run " : " 1 " , " progress " : 0 , " progress_msg " : " " , " chunk_num " : 0 , " token_num " : 0 }
2024-10-12 19:35:19 +08:00
DocumentService . update_by_id ( id , info )
2025-11-06 09:36:38 +08:00
settings . docStoreConn . delete ( { " doc_id " : id } , search . index_name ( tenant_id ) , dataset_id )
2024-10-12 19:35:19 +08:00
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 " ] )
2025-03-14 23:43:46 +08:00
queue_tasks ( doc , bucket , name , 0 )
2025-03-21 14:05:17 +08:00
success_count + = 1
2025-03-19 12:18:19 +08:00
if not_found :
2025-11-04 15:12:53 +08:00
return get_result ( message = f " Documents not found: { not_found } " , code = RetCode . DATA_ERROR )
2025-03-21 14:05:17 +08:00
if duplicate_messages :
if success_count > 0 :
2025-06-05 12:46:29 +08:00
return get_result (
message = f " Partially parsed { success_count } documents with { len ( duplicate_messages ) } errors " ,
data = { " success_count " : success_count , " errors " : duplicate_messages } ,
)
2025-03-21 14:05:17 +08:00
else :
return get_error_data_result ( message = " ; " . join ( duplicate_messages ) )
2025-03-19 12:18:19 +08:00
2024-10-12 19:35:19 +08:00
return get_result ( )
2024-11-04 08:35:36 +01:00
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/chunks " , methods = [ " DELETE " ] ) # noqa: F821
2024-09-14 13:24:21 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def stop_parsing ( tenant_id , dataset_id ) :
2024-11-04 08:35:36 +01:00
"""
Stop parsing documents into chunks.
---
tags:
- Chunks
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: body
name: body
description: Stop parsing parameters.
required: true
schema:
type: object
properties:
document_ids:
type: array
items:
type: string
description: List of document IDs to stop parsing.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Parsing stopped successfully.
schema:
type: object
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2025-03-21 14:05:17 +08:00
2024-10-16 18:41:24 +08:00
if not req . get ( " document_ids " ) :
return get_error_data_result ( " `document_ids` is required " )
2025-03-21 14:05:17 +08:00
doc_list = req . get ( " document_ids " )
unique_doc_ids , duplicate_messages = check_duplicate_ids ( doc_list , " document " )
doc_list = unique_doc_ids
success_count = 0
for id in doc_list :
2024-10-16 18:41:24 +08:00
doc = DocumentService . query ( id = id , kb_id = dataset_id )
if not doc :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the document { id } . " )
2025-01-17 18:28:15 +08:00
if int ( doc [ 0 ] . progress ) == 1 or doc [ 0 ] . progress == 0 :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( " Can ' t stop parsing document with progress at 0 or 1 " )
2025-12-04 19:29:06 +08:00
# Send cancellation signal via Redis to stop background task
cancel_all_task_of ( id )
2024-11-04 08:35:36 +01:00
info = { " run " : " 2 " , " progress " : 0 , " chunk_num " : 0 }
2024-10-12 19:35:19 +08:00
DocumentService . update_by_id ( id , info )
2025-11-06 09:36:38 +08:00
settings . docStoreConn . delete ( { " doc_id " : doc [ 0 ] . id } , search . index_name ( tenant_id ) , dataset_id )
2025-03-21 14:05:17 +08:00
success_count + = 1
if duplicate_messages :
if success_count > 0 :
2025-06-05 12:46:29 +08:00
return get_result (
message = f " Partially stopped { success_count } documents with { len ( duplicate_messages ) } errors " ,
data = { " success_count " : success_count , " errors " : duplicate_messages } ,
)
2025-03-21 14:05:17 +08:00
else :
return get_error_data_result ( message = " ; " . join ( duplicate_messages ) )
2024-10-12 19:35:19 +08:00
return get_result ( )
2024-12-08 21:23:51 +08:00
@manager.route ( " /datasets/<dataset_id>/documents/<document_id>/chunks " , methods = [ " GET " ] ) # noqa: F821
2024-10-12 19:35:19 +08:00
@token_required
2024-11-04 08:35:36 +01:00
def list_chunks ( tenant_id , dataset_id , document_id ) :
"""
List chunks of a document.
---
tags:
- Chunks
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: path
name: document_id
type: string
required: true
description: ID of the document.
- in: query
2024-11-05 14:07:31 +08:00
name: page
2024-11-04 08:35:36 +01:00
type: integer
required: false
default: 1
description: Page number.
- in: query
2024-11-05 14:07:31 +08:00
name: page_size
2024-11-04 08:35:36 +01:00
type: integer
required: false
default: 30
description: Number of items per page.
2025-05-12 11:05:32 +08:00
- in: query
name: id
type: string
required: false
default: " "
2025-12-08 12:21:18 +08:00
description: Chunk id.
2024-11-04 08:35:36 +01:00
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: List of chunks.
schema:
type: object
properties:
total:
type: integer
description: Total number of chunks.
chunks:
type: array
items:
type: object
properties:
id:
type: string
description: Chunk ID.
content:
type: string
description: Chunk content.
document_id:
type: string
description: ID of the document.
important_keywords:
type: array
items:
type: string
description: Important keywords.
image_id:
type: string
description: Image ID associated with the chunk.
doc:
type: object
description: Document details.
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2024-11-04 08:35:36 +01:00
doc = DocumentService . query ( id = document_id , kb_id = dataset_id )
2024-10-12 19:35:19 +08:00
if not doc :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = f " You don ' t own the document { document_id } . " )
2024-11-04 08:35:36 +01:00
doc = doc [ 0 ]
2024-10-12 19:35:19 +08:00
req = request . args
doc_id = document_id
2024-11-04 20:03:14 +08:00
page = int ( req . get ( " page " , 1 ) )
size = int ( req . get ( " page_size " , 30 ) )
2024-09-14 13:24:21 +08:00
question = req . get ( " keywords " , " " )
2024-10-16 18:41:24 +08:00
query = {
2024-11-04 08:35:36 +01:00
" doc_ids " : [ doc_id ] ,
" page " : page ,
" size " : size ,
" question " : question ,
" sort " : True ,
2024-10-16 18:41:24 +08:00
}
2024-10-24 20:10:47 +08:00
key_mapping = {
" chunk_num " : " chunk_count " ,
" kb_id " : " dataset_id " ,
" token_num " : " token_count " ,
2024-11-04 08:35:36 +01:00
" parser_id " : " chunk_method " ,
2024-10-24 20:10:47 +08:00
}
run_mapping = {
" 0 " : " UNSTART " ,
" 1 " : " RUNNING " ,
" 2 " : " CANCEL " ,
" 3 " : " DONE " ,
2024-11-04 08:35:36 +01:00
" 4 " : " FAIL " ,
2024-10-24 20:10:47 +08:00
}
2024-11-04 08:35:36 +01:00
doc = doc . to_dict ( )
2024-10-24 20:10:47 +08:00
renamed_doc = { }
for key , value in doc . items ( ) :
new_key = key_mapping . get ( key , key )
renamed_doc [ new_key ] = value
2024-10-30 16:15:42 +08:00
if key == " run " :
renamed_doc [ " run " ] = run_mapping . get ( str ( value ) )
2024-11-12 14:59:41 +08:00
res = { " total " : 0 , " chunks " : [ ] , " doc " : renamed_doc }
2024-12-30 19:01:44 +08:00
if req . get ( " id " ) :
2025-11-06 09:36:38 +08:00
chunk = settings . docStoreConn . get ( req . get ( " id " ) , search . index_name ( tenant_id ) , [ dataset_id ] )
2025-03-25 19:03:29 +08:00
if not chunk :
2025-11-04 15:12:53 +08:00
return get_result ( message = f " Chunk not found: { dataset_id } / { req . get ( ' id ' ) } " , code = RetCode . NOT_FOUND )
2024-12-30 19:01:44 +08:00
k = [ ]
for n in chunk . keys ( ) :
if re . search ( r " (_vec$|_sm_|_tks|_ltks) " , n ) :
k . append ( n )
for n in k :
del chunk [ n ]
if not chunk :
return get_error_data_result ( f " Chunk ` { req . get ( ' id ' ) } ` not found. " )
2025-06-05 12:46:29 +08:00
res [ " total " ] = 1
2024-12-30 19:01:44 +08:00
final_chunk = {
2025-06-05 12:46:29 +08:00
" id " : chunk . get ( " id " , chunk . get ( " chunk_id " ) ) ,
" content " : chunk [ " content_with_weight " ] ,
" document_id " : chunk . get ( " doc_id " , chunk . get ( " document_id " ) ) ,
" docnm_kwd " : chunk [ " docnm_kwd " ] ,
" important_keywords " : chunk . get ( " important_kwd " , [ ] ) ,
" questions " : chunk . get ( " question_kwd " , [ ] ) ,
" dataset_id " : chunk . get ( " kb_id " , chunk . get ( " dataset_id " ) ) ,
" image_id " : chunk . get ( " img_id " , " " ) ,
" available " : bool ( chunk . get ( " available_int " , 1 ) ) ,
" positions " : chunk . get ( " position_int " , [ ] ) ,
2024-12-30 19:01:44 +08:00
}
res [ " chunks " ] . append ( final_chunk )
_ = Chunk ( * * final_chunk )
2025-12-25 21:18:13 +08:00
elif settings . docStoreConn . index_exist ( search . index_name ( tenant_id ) , dataset_id ) :
2025-11-06 09:36:38 +08:00
sres = settings . retriever . search ( query , search . index_name ( tenant_id ) , [ dataset_id ] , emb_mdl = None , highlight = True )
2024-11-12 14:59:41 +08:00
res [ " total " ] = sres . total
for id in sres . ids :
d = {
" id " : id ,
2025-10-28 09:46:32 +08:00
" content " : ( remove_redundant_spaces ( sres . highlight [ id ] ) if question and id in sres . highlight else sres . field [ id ] . get ( " content_with_weight " , " " ) ) ,
2024-12-30 19:01:44 +08:00
" document_id " : sres . field [ id ] [ " doc_id " ] ,
2024-11-12 14:59:41 +08:00
" docnm_kwd " : sres . field [ id ] [ " docnm_kwd " ] ,
2024-12-30 19:01:44 +08:00
" important_keywords " : sres . field [ id ] . get ( " important_kwd " , [ ] ) ,
" questions " : sres . field [ id ] . get ( " question_kwd " , [ ] ) ,
" dataset_id " : sres . field [ id ] . get ( " kb_id " , sres . field [ id ] . get ( " dataset_id " ) ) ,
" image_id " : sres . field [ id ] . get ( " img_id " , " " ) ,
2025-04-17 17:17:35 +08:00
" available " : bool ( int ( sres . field [ id ] . get ( " available_int " , " 1 " ) ) ) ,
2025-06-05 12:46:29 +08:00
" positions " : sres . field [ id ] . get ( " position_int " , [ ] ) ,
2024-11-12 14:59:41 +08:00
}
2024-12-30 19:01:44 +08:00
res [ " chunks " ] . append ( d )
2025-06-05 12:46:29 +08:00
_ = Chunk ( * * d ) # validate the chunk
2024-10-16 18:41:24 +08:00
return get_result ( data = res )
2024-09-14 13:24:21 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( # noqa: F821
2024-11-04 08:35:36 +01:00
" /datasets/<dataset_id>/documents/<document_id>/chunks " , methods = [ " POST " ]
)
2024-09-14 13:24:21 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def add_chunk ( tenant_id , dataset_id , document_id ) :
2024-11-04 08:35:36 +01:00
"""
Add a chunk to a document.
---
tags:
- Chunks
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: path
name: document_id
type: string
required: true
description: ID of the document.
- in: body
name: body
description: Chunk data.
required: true
schema:
type: object
properties:
content:
type: string
required: true
description: Content of the chunk.
important_keywords:
type: array
items:
type: string
description: Important keywords.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Chunk added successfully.
schema:
type: object
properties:
chunk:
type: object
properties:
id:
type: string
description: Chunk ID.
content:
type: string
description: Chunk content.
document_id:
type: string
description: ID of the document.
important_keywords:
type: array
items:
type: string
description: Important keywords.
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2024-10-12 19:35:19 +08:00
doc = DocumentService . query ( id = document_id , kb_id = dataset_id )
if not doc :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = f " You don ' t own the document { document_id } . " )
2024-10-14 20:03:33 +08:00
doc = doc [ 0 ]
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2025-03-21 14:05:59 +08:00
if not str ( req . get ( " content " , " " ) ) . strip ( ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " `content` is required " )
2024-10-16 18:41:24 +08:00
if " important_keywords " in req :
2024-12-08 14:21:12 +08:00
if not isinstance ( req [ " important_keywords " ] , list ) :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( " `important_keywords` is required to be a list " )
2024-12-05 14:51:19 +08:00
if " questions " in req :
2024-12-08 14:21:12 +08:00
if not isinstance ( req [ " questions " ] , list ) :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( " `questions` is required to be a list " )
2024-12-12 17:47:39 +08:00
chunk_id = xxhash . xxh64 ( ( req [ " content " ] + document_id ) . encode ( " utf-8 " ) ) . hexdigest ( )
2024-11-04 08:35:36 +01:00
d = {
" id " : chunk_id ,
" content_ltks " : rag_tokenizer . tokenize ( req [ " content " ] ) ,
" content_with_weight " : req [ " content " ] ,
}
2024-09-14 13:24:21 +08:00
d [ " content_sm_ltks " ] = rag_tokenizer . fine_grained_tokenize ( d [ " content_ltks " ] )
2024-10-16 18:41:24 +08:00
d [ " important_kwd " ] = req . get ( " important_keywords " , [ ] )
2025-06-05 12:46:29 +08:00
d [ " important_tks " ] = rag_tokenizer . tokenize ( " " . join ( req . get ( " important_keywords " , [ ] ) ) )
2025-03-21 18:44:12 +08:00
d [ " question_kwd " ] = [ str ( q ) . strip ( ) for q in req . get ( " questions " , [ ] ) if str ( q ) . strip ( ) ]
2025-06-05 12:46:29 +08:00
d [ " question_tks " ] = rag_tokenizer . tokenize ( " \n " . join ( req . get ( " questions " , [ ] ) ) )
2024-09-14 13:24:21 +08:00
d [ " create_time " ] = str ( datetime . datetime . now ( ) ) . replace ( " T " , " " ) [ : 19 ]
d [ " create_timestamp_flt " ] = datetime . datetime . now ( ) . timestamp ( )
2024-11-12 14:59:41 +08:00
d [ " kb_id " ] = dataset_id
2024-10-12 19:35:19 +08:00
d [ " docnm_kwd " ] = doc . name
2024-11-12 14:59:41 +08:00
d [ " doc_id " ] = document_id
2024-10-12 19:35:19 +08:00
embd_id = DocumentService . get_embd_id ( document_id )
2025-06-05 12:46:29 +08:00
embd_mdl = TenantLLMService . model_instance ( tenant_id , LLMType . EMBEDDING . value , embd_id )
2024-12-05 14:51:19 +08:00
v , c = embd_mdl . encode ( [ doc . name , req [ " content " ] if not d [ " question_kwd " ] else " \n " . join ( d [ " question_kwd " ] ) ] )
2024-10-12 19:35:19 +08:00
v = 0.1 * v [ 0 ] + 0.9 * v [ 1 ]
d [ " q_ %d _vec " % len ( v ) ] = v . tolist ( )
2025-11-06 09:36:38 +08:00
settings . docStoreConn . insert ( [ d ] , search . index_name ( tenant_id ) , dataset_id )
2024-10-12 19:35:19 +08:00
2024-11-04 08:35:36 +01:00
DocumentService . increment_chunk_num ( doc . id , doc . kb_id , c , 1 , 0 )
2024-10-12 19:35:19 +08:00
# rename keys
key_mapping = {
2024-11-12 14:59:41 +08:00
" id " : " id " ,
2024-10-12 19:35:19 +08:00
" content_with_weight " : " content " ,
" doc_id " : " document_id " ,
" important_kwd " : " important_keywords " ,
2024-12-05 14:51:19 +08:00
" question_kwd " : " questions " ,
2024-10-12 19:35:19 +08:00
" kb_id " : " dataset_id " ,
" create_timestamp_flt " : " create_timestamp " ,
" create_time " : " create_time " ,
2024-11-04 08:35:36 +01:00
" document_keyword " : " document " ,
2024-10-12 19:35:19 +08:00
}
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
2024-12-20 22:55:45 +08:00
_ = Chunk ( * * renamed_chunk ) # validate the chunk
2024-10-12 19:35:19 +08:00
return get_result ( data = { " chunk " : renamed_chunk } )
# return get_result(data={"chunk_id": chunk_id})
2024-09-14 13:24:21 +08:00
2024-09-18 11:08:19 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( # noqa: F821
2024-11-04 08:35:36 +01:00
" datasets/<dataset_id>/documents/<document_id>/chunks " , methods = [ " DELETE " ]
)
2024-09-14 13:24:21 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def rm_chunk ( tenant_id , dataset_id , document_id ) :
2024-11-04 08:35:36 +01:00
"""
Remove chunks from a document.
---
tags:
- Chunks
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: path
name: document_id
type: string
required: true
description: ID of the document.
- in: body
name: body
description: Chunk removal parameters.
required: true
schema:
type: object
properties:
chunk_ids:
type: array
items:
type: string
description: List of chunk IDs to remove.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Chunks removed successfully.
schema:
type: object
"""
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2025-03-28 12:13:43 +08:00
docs = DocumentService . get_by_ids ( [ document_id ] )
if not docs :
raise LookupError ( f " Can ' t find the document with ID { document_id } ! " )
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2024-11-12 14:59:41 +08:00
condition = { " doc_id " : document_id }
if " chunk_ids " in req :
2025-03-21 14:05:17 +08:00
unique_chunk_ids , duplicate_messages = check_duplicate_ids ( req [ " chunk_ids " ] , " chunk " )
condition [ " id " ] = unique_chunk_ids
2025-12-29 13:18:23 +08:00
else :
unique_chunk_ids = [ ]
duplicate_messages = [ ]
2025-11-06 09:36:38 +08:00
chunk_number = settings . docStoreConn . delete ( condition , search . index_name ( tenant_id ) , dataset_id )
2024-11-12 14:59:41 +08:00
if chunk_number != 0 :
DocumentService . decrement_chunk_num ( document_id , dataset_id , 1 , chunk_number , 0 )
2025-03-21 14:05:17 +08:00
if " chunk_ids " in req and chunk_number != len ( unique_chunk_ids ) :
2025-04-02 19:20:17 +08:00
if len ( unique_chunk_ids ) == 0 :
return get_result ( message = f " deleted { chunk_number } chunks " )
2025-03-21 14:05:17 +08:00
return get_error_data_result ( message = f " rm_chunk deleted chunks { chunk_number } , expect { len ( unique_chunk_ids ) } " )
if duplicate_messages :
2025-06-05 12:46:29 +08:00
return get_result (
message = f " Partially deleted { chunk_number } chunks with { len ( duplicate_messages ) } errors " ,
data = { " success_count " : chunk_number , " errors " : duplicate_messages } ,
)
2024-11-12 14:59:41 +08:00
return get_result ( message = f " deleted { chunk_number } chunks " )
2024-10-12 19:35:19 +08:00
2024-12-08 21:23:51 +08:00
@manager.route ( # noqa: F821
2024-11-04 08:35:36 +01:00
" /datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id> " , methods = [ " PUT " ]
)
2024-09-18 11:08:19 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def update_chunk ( tenant_id , dataset_id , document_id , chunk_id ) :
2024-11-04 08:35:36 +01:00
"""
Update a chunk within a document.
---
tags:
- Chunks
security:
- ApiKeyAuth: []
parameters:
- in: path
name: dataset_id
type: string
required: true
description: ID of the dataset.
- in: path
name: document_id
type: string
required: true
description: ID of the document.
- in: path
name: chunk_id
type: string
required: true
description: ID of the chunk to update.
- in: body
name: body
description: Chunk update parameters.
required: true
schema:
type: object
properties:
content:
type: string
description: Updated content of the chunk.
important_keywords:
type: array
items:
type: string
description: Updated important keywords.
available:
type: boolean
description: Availability status of the chunk.
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Chunk updated successfully.
schema:
type: object
"""
2025-11-06 09:36:38 +08:00
chunk = settings . docStoreConn . get ( chunk_id , search . index_name ( tenant_id ) , [ dataset_id ] )
2024-11-12 14:59:41 +08:00
if chunk is None :
2024-10-16 18:41:24 +08:00
return get_error_data_result ( f " Can ' t find this chunk { chunk_id } " )
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = dataset_id , user_id = tenant_id ) :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = f " You don ' t own the dataset { dataset_id } . " )
2024-10-12 19:35:19 +08:00
doc = DocumentService . query ( id = document_id , kb_id = dataset_id )
if not doc :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = f " You don ' t own the document { document_id } . " )
2024-10-16 18:41:24 +08:00
doc = doc [ 0 ]
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2025-11-26 11:06:37 +08:00
if " content " in req and req [ " content " ] is not None :
2024-11-12 14:59:41 +08:00
content = req [ " content " ]
else :
content = chunk . get ( " content_with_weight " , " " )
d = { " id " : chunk_id , " content_with_weight " : content }
2024-10-16 18:41:24 +08:00
d [ " content_ltks " ] = rag_tokenizer . tokenize ( d [ " content_with_weight " ] )
2024-09-18 11:08:19 +08:00
d [ " content_sm_ltks " ] = rag_tokenizer . fine_grained_tokenize ( d [ " content_ltks " ] )
2024-10-16 18:41:24 +08:00
if " important_keywords " in req :
2024-11-04 08:35:36 +01:00
if not isinstance ( req [ " important_keywords " ] , list ) :
2024-10-23 12:02:18 +08:00
return get_error_data_result ( " `important_keywords` should be a list " )
2024-12-05 14:51:19 +08:00
d [ " important_kwd " ] = req . get ( " important_keywords " , [ ] )
2024-10-16 18:41:24 +08:00
d [ " important_tks " ] = rag_tokenizer . tokenize ( " " . join ( req [ " important_keywords " ] ) )
2024-12-05 14:51:19 +08:00
if " questions " in req :
if not isinstance ( req [ " questions " ] , list ) :
return get_error_data_result ( " `questions` should be a list " )
2025-04-03 18:04:19 +08:00
d [ " question_kwd " ] = [ str ( q ) . strip ( ) for q in req . get ( " questions " , [ ] ) if str ( q ) . strip ( ) ]
2024-12-05 14:51:19 +08:00
d [ " question_tks " ] = rag_tokenizer . tokenize ( " \n " . join ( req [ " questions " ] ) )
2024-09-29 10:13:07 +08:00
if " available " in req :
2024-10-23 12:02:18 +08:00
d [ " available_int " ] = int ( req [ " available " ] )
2025-11-03 11:01:44 +08:00
if " positions " in req :
if not isinstance ( req [ " positions " ] , list ) :
return get_error_data_result ( " `positions` should be a list " )
d [ " position_int " ] = req [ " positions " ]
2024-10-12 19:35:19 +08:00
embd_id = DocumentService . get_embd_id ( document_id )
2025-06-05 12:46:29 +08:00
embd_mdl = TenantLLMService . model_instance ( tenant_id , LLMType . EMBEDDING . value , embd_id )
2024-10-12 19:35:19 +08:00
if doc . parser_id == ParserType . QA :
2024-11-04 08:35:36 +01:00
arr = [ t for t in re . split ( r " [ \ n \ t] " , d [ " content_with_weight " ] ) if len ( t ) > 1 ]
2024-10-12 19:35:19 +08:00
if len ( arr ) != 2 :
2025-06-05 12:46:29 +08:00
return get_error_data_result ( message = " Q&A must be separated by TAB/ENTER key. " )
2024-10-12 19:35:19 +08:00
q , a = rmPrefix ( arr [ 0 ] ) , rmPrefix ( arr [ 1 ] )
2025-06-05 12:46:29 +08:00
d = beAdoc ( d , arr [ 0 ] , arr [ 1 ] , not any ( [ rag_tokenizer . is_chinese ( t ) for t in q + a ] ) )
2024-10-12 19:35:19 +08:00
2024-12-05 14:51:19 +08:00
v , c = embd_mdl . encode ( [ doc . name , d [ " content_with_weight " ] if not d . get ( " question_kwd " ) else " \n " . join ( d [ " question_kwd " ] ) ] )
2024-10-12 19:35:19 +08:00
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 ( )
2025-11-06 09:36:38 +08:00
settings . docStoreConn . update ( { " id " : chunk_id } , d , search . index_name ( tenant_id ) , dataset_id )
2024-10-12 19:35:19 +08:00
return get_result ( )
2024-12-08 21:23:51 +08:00
@manager.route ( " /retrieval " , methods = [ " POST " ] ) # noqa: F821
2024-09-18 11:08:19 +08:00
@token_required
2025-11-18 17:05:16 +08:00
async def retrieval_test ( tenant_id ) :
2024-11-04 08:35:36 +01:00
"""
Retrieve chunks based on a query.
---
tags:
- Retrieval
security:
- ApiKeyAuth: []
parameters:
- in: body
name: body
description: Retrieval parameters.
required: true
schema:
type: object
properties:
dataset_ids:
type: array
items:
type: string
required: true
description: List of dataset IDs to search in.
question:
type: string
required: true
description: Query string.
document_ids:
type: array
items:
type: string
description: List of document IDs to filter.
similarity_threshold:
type: number
format: float
description: Similarity threshold.
vector_similarity_weight:
type: number
format: float
description: Vector similarity weight.
top_k:
type: integer
description: Maximum number of chunks to return.
highlight:
type: boolean
description: Whether to highlight matched content.
2025-09-05 11:12:15 +08:00
metadata_condition:
type: object
description: metadata filter condition.
2024-11-04 08:35:36 +01:00
- in: header
name: Authorization
type: string
required: true
description: Bearer token for authentication.
responses:
200:
description: Retrieval results.
schema:
type: object
properties:
chunks:
type: array
items:
type: object
properties:
id:
type: string
description: Chunk ID.
content:
type: string
description: Chunk content.
document_id:
type: string
description: ID of the document.
dataset_id:
type: string
description: ID of the dataset.
similarity:
type: number
format: float
description: Similarity score.
"""
2025-12-01 14:24:06 +08:00
req = await get_request_json ( )
2024-10-24 20:05:21 +08:00
if not req . get ( " dataset_ids " ) :
2024-11-01 22:59:17 +08:00
return get_error_data_result ( " `dataset_ids` is required. " )
2024-10-24 20:05:21 +08:00
kb_ids = req [ " dataset_ids " ]
2024-11-04 08:35:36 +01:00
if not isinstance ( kb_ids , list ) :
2024-11-01 22:59:17 +08:00
return get_error_data_result ( " `dataset_ids` should be a list " )
2024-10-24 20:05:21 +08:00
for id in kb_ids :
2024-11-07 19:26:03 +08:00
if not KnowledgebaseService . accessible ( kb_id = id , user_id = tenant_id ) :
2024-10-24 20:05:21 +08:00
return get_error_data_result ( f " You don ' t own the dataset { id } . " )
2025-01-22 19:43:14 +08:00
kbs = KnowledgebaseService . get_by_ids ( kb_ids )
2025-02-20 12:40:59 +08:00
embd_nms = list ( set ( [ TenantLLMService . split_model_name_and_factory ( kb . embd_id ) [ 0 ] for kb in kbs ] ) ) # remove vendor suffix for comparison
2024-10-21 14:29:06 +08:00
if len ( embd_nms ) != 1 :
return get_result (
2024-11-05 11:02:31 +08:00
message = ' Datasets use different embedding models. " ' ,
2025-11-04 15:12:53 +08:00
code = RetCode . DATA_ERROR ,
2024-11-04 08:35:36 +01:00
)
2024-10-16 18:41:24 +08:00
if " question " not in req :
2024-10-12 19:35:19 +08:00
return get_error_data_result ( " `question` is required. " )
2024-11-04 20:03:14 +08:00
page = int ( req . get ( " page " , 1 ) )
2024-11-05 14:07:31 +08:00
size = int ( req . get ( " page_size " , 30 ) )
2024-10-16 10:21:08 +08:00
question = req [ " question " ]
2024-10-24 20:05:21 +08:00
doc_ids = req . get ( " document_ids " , [ ] )
2025-01-22 19:43:14 +08:00
use_kg = req . get ( " use_kg " , False )
2025-11-21 14:51:58 +08:00
toc_enhance = req . get ( " toc_enhance " , False )
2025-07-21 17:25:28 +08:00
langs = req . get ( " cross_languages " , [ ] )
2024-11-04 08:35:36 +01:00
if not isinstance ( doc_ids , list ) :
2026-01-08 13:22:58 +08:00
return get_error_data_result ( " `documents` should be a list " )
if doc_ids :
doc_ids_list = KnowledgebaseService . list_documents_by_ids ( kb_ids )
for doc_id in doc_ids :
if doc_id not in doc_ids_list :
return get_error_data_result ( f " The datasets don ' t own the document { doc_id } " )
2025-09-05 11:12:15 +08:00
if not doc_ids :
2025-11-20 14:31:12 +08:00
metadata_condition = req . get ( " metadata_condition " , { } ) or { }
2025-09-05 11:12:15 +08:00
metas = DocumentService . get_meta_by_kbs ( kb_ids )
2025-11-20 14:31:12 +08:00
doc_ids = meta_filter ( metas , convert_conditions ( metadata_condition ) , metadata_condition . get ( " logic " , " and " ) )
2025-11-28 14:04:14 +08:00
# If metadata_condition has conditions but no docs match, return empty result
if not doc_ids and metadata_condition . get ( " conditions " ) :
return get_result ( data = { " total " : 0 , " chunks " : [ ] , " doc_aggs " : { } } )
2025-11-20 19:51:25 +08:00
if metadata_condition and not doc_ids :
doc_ids = [ " -999 " ]
2024-10-16 10:21:08 +08:00
similarity_threshold = float ( req . get ( " similarity_threshold " , 0.2 ) )
2024-09-18 11:08:19 +08:00
vector_similarity_weight = float ( req . get ( " vector_similarity_weight " , 0.3 ) )
top = int ( req . get ( " top_k " , 1024 ) )
2024-11-04 08:35:36 +01:00
if req . get ( " highlight " ) == " False " or req . get ( " highlight " ) == " false " :
2024-10-14 20:03:33 +08:00
highlight = False
else :
highlight = True
2024-09-18 11:08:19 +08:00
try :
2025-05-07 16:05:40 +08:00
tenant_ids = list ( set ( [ kb . tenant_id for kb in kbs ] ) )
2024-10-21 14:29:06 +08:00
e , kb = KnowledgebaseService . get_by_id ( kb_ids [ 0 ] )
2024-09-18 11:08:19 +08:00
if not e :
2024-11-05 11:02:31 +08:00
return get_error_data_result ( message = " Dataset not found! " )
2025-01-08 11:27:46 +08:00
embd_mdl = LLMBundle ( kb . tenant_id , LLMType . EMBEDDING , llm_name = kb . embd_id )
2024-09-18 11:08:19 +08:00
rerank_mdl = None
if req . get ( " rerank_id " ) :
2025-01-08 11:27:46 +08:00
rerank_mdl = LLMBundle ( kb . tenant_id , LLMType . RERANK , llm_name = req [ " rerank_id " ] )
2024-09-18 11:08:19 +08:00
2025-07-21 17:25:28 +08:00
if langs :
2025-12-11 17:38:17 +08:00
question = await cross_languages ( kb . tenant_id , None , question , langs )
2025-07-21 17:25:28 +08:00
2024-09-18 11:08:19 +08:00
if req . get ( " keyword " , False ) :
2025-01-08 11:27:46 +08:00
chat_mdl = LLMBundle ( kb . tenant_id , LLMType . CHAT )
2025-12-11 17:38:17 +08:00
question + = await keyword_extraction ( chat_mdl , question )
2024-09-18 11:08:19 +08:00
2025-11-06 09:36:38 +08:00
ranks = settings . retriever . retrieval (
2024-11-04 08:35:36 +01:00
question ,
embd_mdl ,
2025-05-07 16:05:40 +08:00
tenant_ids ,
2024-11-04 08:35:36 +01:00
kb_ids ,
page ,
size ,
similarity_threshold ,
vector_similarity_weight ,
top ,
doc_ids ,
rerank_mdl = rerank_mdl ,
highlight = highlight ,
2025-06-05 12:46:29 +08:00
rank_feature = label_question ( question , kbs ) ,
2024-11-04 08:35:36 +01:00
)
2025-11-21 14:51:58 +08:00
if toc_enhance :
chat_mdl = LLMBundle ( kb . tenant_id , LLMType . CHAT )
2026-01-07 15:35:30 +08:00
cks = await settings . retriever . retrieval_by_toc ( question , ranks [ " chunks " ] , tenant_ids , chat_mdl , size )
2025-11-21 14:51:58 +08:00
if cks :
ranks [ " chunks " ] = cks
2025-01-22 19:43:14 +08:00
if use_kg :
2025-12-31 14:40:27 +08:00
ck = await settings . kg_retriever . retrieval ( question , [ k . tenant_id for k in kbs ] , kb_ids , embd_mdl , LLMBundle ( kb . tenant_id , LLMType . CHAT ) )
2025-01-22 19:43:14 +08:00
if ck [ " content_with_weight " ] :
ranks [ " chunks " ] . insert ( 0 , ck )
2024-09-18 11:08:19 +08:00
for c in ranks [ " chunks " ] :
2024-11-19 14:15:25 +08:00
c . pop ( " vector " , None )
2024-09-18 11:08:19 +08:00
##rename keys
2024-10-12 19:35:19 +08:00
renamed_chunks = [ ]
2024-09-18 11:08:19 +08:00
for chunk in ranks [ " chunks " ] :
key_mapping = {
" chunk_id " : " id " ,
" content_with_weight " : " content " ,
" doc_id " : " document_id " ,
" important_kwd " : " important_keywords " ,
2024-12-05 14:51:19 +08:00
" question_kwd " : " questions " ,
2024-11-04 08:35:36 +01:00
" docnm_kwd " : " document_keyword " ,
2025-06-05 12:46:29 +08:00
" kb_id " : " dataset_id " ,
2024-09-18 11:08:19 +08:00
}
2024-10-12 19:35:19 +08:00
rename_chunk = { }
2024-09-18 11:08:19 +08:00
for key , value in chunk . items ( ) :
new_key = key_mapping . get ( key , key )
rename_chunk [ new_key ] = value
2024-10-14 20:03:33 +08:00
renamed_chunks . append ( rename_chunk )
2024-09-18 11:08:19 +08:00
ranks [ " chunks " ] = renamed_chunks
2024-10-12 19:35:19 +08:00
return get_result ( data = ranks )
2024-09-18 11:08:19 +08:00
except Exception as e :
if str ( e ) . find ( " not_found " ) > 0 :
2024-11-04 08:35:36 +01:00
return get_result (
2024-11-05 11:02:31 +08:00
message = " No chunk found! Check the chunk status please! " ,
2025-11-04 15:12:53 +08:00
code = RetCode . DATA_ERROR ,
2024-11-04 08:35:36 +01:00
)
2024-11-15 17:30:56 +08:00
return server_error_response ( e )