11from __future__ import annotations
22
33import asyncio
4+ import shutil
45import traceback
56import uuid
67from pathlib import Path
1112from astrbot .core import logger
1213from astrbot .core .core_lifecycle import AstrBotCoreLifecycle
1314from astrbot .core .provider .provider import EmbeddingProvider , RerankProvider
14- from astrbot .core .utils .astrbot_path import get_astrbot_temp_path
15+ from astrbot .core .utils .astrbot_path import get_astrbot_system_tmp_path
1516from astrbot .dashboard .schemas import KnowledgeBaseRequest
1617from astrbot .dashboard .utils import generate_tsne_visualization
1718
@@ -116,17 +117,45 @@ def format_failed_doc_error(file_name: str, error: Exception) -> str:
116117 return message
117118 return f"{ file_name } : { message } "
118119
120+ @staticmethod
121+ def _cleanup_staging_dir (staging_dir : Path ) -> None :
122+ """Remove a knowledge base upload staging directory.
123+
124+ Args:
125+ staging_dir: Task-specific system temporary directory to remove.
126+ """
127+ try :
128+ shutil .rmtree (staging_dir )
129+ except FileNotFoundError :
130+ pass
131+ except OSError as exc :
132+ logger .warning (f"Failed to clean upload staging directory: { exc } " )
133+
119134 async def background_upload_task (
120135 self ,
121136 task_id : str ,
122137 kb_helper ,
123138 files_to_upload : list [dict [str , Any ]],
139+ staging_dir : Path ,
124140 chunk_size : int ,
125141 chunk_overlap : int ,
126142 batch_size : int ,
127143 tasks_limit : int ,
128144 max_retries : int ,
129145 ) -> None :
146+ """Process staged knowledge base files one at a time.
147+
148+ Args:
149+ task_id: Identifier used to report upload progress and results.
150+ kb_helper: Knowledge base helper that parses and stores documents.
151+ files_to_upload: Metadata and temporary paths for staged files.
152+ staging_dir: Task-specific system temporary directory.
153+ chunk_size: Maximum size of each generated document chunk.
154+ chunk_overlap: Number of overlapping characters between chunks.
155+ batch_size: Number of chunks sent in each embedding batch.
156+ tasks_limit: Maximum number of concurrent embedding tasks.
157+ max_retries: Maximum retries for embedding operations.
158+ """
130159 try :
131160 self .init_task (task_id , status = "processing" )
132161 self .upload_progress [task_id ] = {
@@ -142,7 +171,11 @@ async def background_upload_task(
142171 failed_docs = []
143172
144173 for file_idx , file_info in enumerate (files_to_upload ):
174+ file_content = None
145175 try :
176+ temp_file_path = Path (file_info ["temp_file_path" ])
177+ async with aiofiles .open (temp_file_path , "rb" ) as file_obj :
178+ file_content = await file_obj .read ()
146179 self .update_progress (
147180 task_id ,
148181 status = "processing" ,
@@ -157,7 +190,7 @@ async def background_upload_task(
157190 )
158191 doc = await kb_helper .upload_document (
159192 file_name = file_info ["file_name" ],
160- file_content = file_info [ " file_content" ] ,
193+ file_content = file_content ,
161194 file_type = file_info ["file_type" ],
162195 chunk_size = chunk_size ,
163196 chunk_overlap = chunk_overlap ,
@@ -177,6 +210,10 @@ async def background_upload_task(
177210 ),
178211 },
179212 )
213+ finally :
214+ # Release the current file before reading the next one.
215+ file_content = None
216+ Path (file_info ["temp_file_path" ]).unlink (missing_ok = True )
180217
181218 self .set_task_result (
182219 task_id ,
@@ -194,6 +231,8 @@ async def background_upload_task(
194231 logger .error (f"后台上传任务 { task_id } 失败: { exc } " )
195232 logger .error (traceback .format_exc ())
196233 self .set_task_result (task_id , "failed" , error = str (exc ))
234+ finally :
235+ self ._cleanup_staging_dir (staging_dir )
197236
198237 async def background_import_task (
199238 self ,
@@ -520,52 +559,62 @@ async def upload_document(
520559 file_list .extend (files .getlist (key ))
521560 if not file_list :
522561 raise KnowledgeBaseServiceError ("缺少文件" )
523- if len (file_list ) > 10 :
524- raise KnowledgeBaseServiceError ("最多只能上传10个文件" )
525562
563+ task_id = str (uuid .uuid4 ())
564+ system_temp_root = Path (get_astrbot_system_tmp_path ())
565+ system_temp_root .mkdir (mode = 0o700 , parents = True , exist_ok = True )
566+ staging_dir = system_temp_root / f"kb_upload_{ task_id } "
567+ staging_dir .mkdir (mode = 0o700 )
526568 files_to_upload = []
527- for file in file_list :
528- file_name = Path (str (file .filename or "document" ).replace ("\\ " , "/" )).name
529- if file_name in {"" , "." , ".." }:
530- file_name = "document"
531- temp_file_path = (
532- Path (get_astrbot_temp_path ()) / f"kb_upload_{ uuid .uuid4 ()} _{ file_name } "
533- )
534- await file .save (temp_file_path )
535- try :
536- async with aiofiles .open (temp_file_path , "rb" ) as file_obj :
537- file_content = await file_obj .read ()
569+ try :
570+ for file in file_list :
571+ file_name = Path (
572+ str (file .filename or "document" ).replace ("\\ " , "/" )
573+ ).name
574+ if file_name in {"" , "." , ".." }:
575+ file_name = "document"
576+ temp_file_path = staging_dir / f"{ uuid .uuid4 ()} _{ file_name } "
538577 file_type = (
539578 file_name .rsplit ("." , 1 )[- 1 ].lower () if "." in file_name else ""
540579 )
541580 files_to_upload .append (
542581 {
543582 "file_name" : file_name ,
544- "file_content " : file_content ,
583+ "temp_file_path " : temp_file_path ,
545584 "file_type" : file_type ,
546585 },
547586 )
548- finally :
549- temp_file_path .unlink (missing_ok = True )
587+ await file .save (temp_file_path )
588+ except Exception :
589+ self ._cleanup_staging_dir (staging_dir )
590+ raise
550591
551- kb_helper = await self .get_kb_manager ().get_kb (kb_id )
552- if not kb_helper :
553- raise KnowledgeBaseServiceError ("知识库不存在" )
592+ try :
593+ kb_helper = await self .get_kb_manager ().get_kb (kb_id )
594+ if not kb_helper :
595+ raise KnowledgeBaseServiceError ("知识库不存在" )
596+ except Exception :
597+ self ._cleanup_staging_dir (staging_dir )
598+ raise
554599
555- task_id = str (uuid .uuid4 ())
556- self .init_task (task_id , status = "pending" )
557- asyncio .create_task (
558- self .background_upload_task (
559- task_id = task_id ,
560- kb_helper = kb_helper ,
561- files_to_upload = files_to_upload ,
562- chunk_size = chunk_size ,
563- chunk_overlap = chunk_overlap ,
564- batch_size = batch_size ,
565- tasks_limit = tasks_limit ,
566- max_retries = max_retries ,
567- ),
568- )
600+ try :
601+ self .init_task (task_id , status = "pending" )
602+ asyncio .create_task (
603+ self .background_upload_task (
604+ task_id = task_id ,
605+ kb_helper = kb_helper ,
606+ files_to_upload = files_to_upload ,
607+ staging_dir = staging_dir ,
608+ chunk_size = chunk_size ,
609+ chunk_overlap = chunk_overlap ,
610+ batch_size = batch_size ,
611+ tasks_limit = tasks_limit ,
612+ max_retries = max_retries ,
613+ ),
614+ )
615+ except Exception :
616+ self ._cleanup_staging_dir (staging_dir )
617+ raise
569618 return {
570619 "task_id" : task_id ,
571620 "file_count" : len (files_to_upload ),
0 commit comments