deepdata: core product code (backend, frontend, runtime, deepflow module)
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
公共工具模块
|
||||
|
||||
提供文本清理、元数据构建、批量处理等工具类。
|
||||
"""
|
||||
from .text_cleaner import TextCleaner
|
||||
from .metadata_builder import MetadataBuilder
|
||||
from .batch_processor import BatchProcessor
|
||||
from .file_utils import get_file_path
|
||||
|
||||
__all__ = [
|
||||
"TextCleaner",
|
||||
"MetadataBuilder",
|
||||
"BatchProcessor",
|
||||
"get_file_path",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
批量处理工具
|
||||
|
||||
提供分批处理文档分块的工具方法。
|
||||
"""
|
||||
import uuid
|
||||
import logging
|
||||
from typing import List
|
||||
from app.module.rag.infra.vectorstore import chunks_to_documents
|
||||
from app.module.rag.infra.document.types import DocumentChunk
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BatchProcessor:
|
||||
"""批量处理工具类"""
|
||||
|
||||
DEFAULT_BATCH_SIZE: int = 20
|
||||
|
||||
@staticmethod
|
||||
async def store_in_batches(
|
||||
vectorstore,
|
||||
chunks: List[DocumentChunk],
|
||||
batch_size: int = DEFAULT_BATCH_SIZE
|
||||
) -> int:
|
||||
"""分批存储分块到向量数据库
|
||||
|
||||
Args:
|
||||
vectorstore: 向量存储实例
|
||||
chunks: 分块列表
|
||||
batch_size: 批次大小
|
||||
|
||||
Returns:
|
||||
成功存储的分块数量
|
||||
"""
|
||||
if not chunks:
|
||||
return 0
|
||||
|
||||
batch_size = batch_size or BatchProcessor.DEFAULT_BATCH_SIZE
|
||||
total_chunks = len(chunks)
|
||||
stored_count = 0
|
||||
|
||||
for batch_start in range(0, total_chunks, batch_size):
|
||||
batch_end = min(batch_start + batch_size, total_chunks)
|
||||
batch_chunks = chunks[batch_start:batch_end]
|
||||
|
||||
await BatchProcessor._store_single_batch(
|
||||
vectorstore,
|
||||
batch_chunks,
|
||||
batch_start,
|
||||
batch_end,
|
||||
total_chunks
|
||||
)
|
||||
stored_count += len(batch_chunks)
|
||||
|
||||
logger.info("批量存储完成,总数量: %d", stored_count)
|
||||
return stored_count
|
||||
|
||||
@staticmethod
|
||||
async def _store_single_batch(
|
||||
vectorstore,
|
||||
batch_chunks: List[DocumentChunk],
|
||||
batch_start: int,
|
||||
batch_end: int,
|
||||
total_chunks: int,
|
||||
) -> None:
|
||||
"""存储单个批次
|
||||
|
||||
Args:
|
||||
vectorstore: 向量存储实例
|
||||
batch_chunks: 批次分块列表
|
||||
batch_start: 批次起始索引
|
||||
batch_end: 批次结束索引
|
||||
total_chunks: 总分块数
|
||||
"""
|
||||
logger.info("处理分块批次 %d-%d / %d", batch_start + 1, batch_end, total_chunks)
|
||||
|
||||
ids = [str(uuid.uuid4()) for _ in batch_chunks]
|
||||
documents, doc_ids = chunks_to_documents(batch_chunks, ids=ids)
|
||||
|
||||
try:
|
||||
# 使用异步方法避免阻塞事件循环
|
||||
await vectorstore.aadd_documents(documents=documents, ids=doc_ids)
|
||||
logger.info("批次 %d-%d 存储成功", batch_start + 1, batch_end)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"批次 %d-%d 存储失败: %s\n第一个文档内容: %.200s",
|
||||
batch_start + 1,
|
||||
batch_end,
|
||||
str(e),
|
||||
documents[0].page_content if documents else "N/A"
|
||||
)
|
||||
raise
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
文件工具类
|
||||
|
||||
提供文件路径提取等通用工具方法。
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def get_file_path(rag_file) -> Optional[str]:
|
||||
"""获取 RAG 文件的绝对路径
|
||||
|
||||
Args:
|
||||
rag_file: RAG 文件实体(需要有 file_metadata 属性)
|
||||
|
||||
Returns:
|
||||
文件绝对路径,不存在返回 None
|
||||
"""
|
||||
if not rag_file.file_metadata:
|
||||
return None
|
||||
|
||||
file_path = rag_file.file_metadata.get("file_path")
|
||||
if file_path:
|
||||
return str(Path(file_path).absolute())
|
||||
return None
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
元数据构建工具
|
||||
|
||||
提供构建和管理分块元数据的工具方法。
|
||||
"""
|
||||
from typing import Dict, Any
|
||||
from app.db.models.knowledge_gen import KnowledgeBase, RagFile
|
||||
|
||||
|
||||
class MetadataBuilder:
|
||||
"""元数据构建工具类"""
|
||||
|
||||
@staticmethod
|
||||
def build_chunk_metadata(rag_file: RagFile, knowledge_base: KnowledgeBase) -> Dict[str, Any]:
|
||||
"""构建分块基础元数据
|
||||
|
||||
Args:
|
||||
rag_file: RAG 文件实体
|
||||
knowledge_base: 知识库实体
|
||||
|
||||
Returns:
|
||||
元数据字典
|
||||
"""
|
||||
return {
|
||||
"rag_file_id": str(rag_file.id),
|
||||
"original_file_id": str(rag_file.file_id),
|
||||
"knowledge_base_id": str(knowledge_base.id),
|
||||
"file_name": rag_file.file_name,
|
||||
"knowledge_base_name": knowledge_base.name,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def add_to_chunks(chunks: list, base_metadata: Dict[str, Any]) -> None:
|
||||
"""为基础元数据添加到分块列表
|
||||
|
||||
Args:
|
||||
chunks: 分块列表
|
||||
base_metadata: 基础元数据
|
||||
"""
|
||||
for chunk in chunks:
|
||||
if hasattr(chunk, 'metadata') and isinstance(chunk.metadata, dict):
|
||||
chunk.metadata.update(base_metadata)
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
文本清理工具
|
||||
|
||||
提供文本清理和标准化的工具方法。
|
||||
"""
|
||||
import re
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TextCleaner:
|
||||
"""文本清理工具类"""
|
||||
|
||||
@staticmethod
|
||||
def clean(text: Optional[str]) -> str:
|
||||
"""清理文本
|
||||
|
||||
Args:
|
||||
text: 原始文本
|
||||
|
||||
Returns:
|
||||
清理后的文本
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
text = TextCleaner._remove_control_characters(text)
|
||||
text = TextCleaner._normalize_whitespace(text)
|
||||
text = TextCleaner._remove_empty_lines(text)
|
||||
|
||||
return text.strip()
|
||||
|
||||
@staticmethod
|
||||
def _remove_control_characters(text: str) -> str:
|
||||
"""移除控制字符"""
|
||||
control_chars = re.compile(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f-\x9f]')
|
||||
return control_chars.sub('', text)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_whitespace(text: str) -> str:
|
||||
"""标准化空白字符"""
|
||||
text = re.sub(r'[ \t]+', ' ', text)
|
||||
text = re.sub(r'\n\s*\n\s*\n+', '\n\n', text)
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def _remove_empty_lines(text: str) -> str:
|
||||
"""移除空行"""
|
||||
lines = text.split('\n')
|
||||
return '\n'.join(line for line in lines if line.strip())
|
||||
|
||||
@staticmethod
|
||||
def has_printable_content(text: str) -> bool:
|
||||
"""检查是否包含可打印内容
|
||||
|
||||
Args:
|
||||
text: 文本
|
||||
|
||||
Returns:
|
||||
是否包含可打印内容
|
||||
"""
|
||||
if not text:
|
||||
return False
|
||||
return any(c.isprintable() and not c.isspace() for c in text)
|
||||
Reference in New Issue
Block a user