2025-07-23 19:21:45 +08:00
|
|
|
|
import os
|
|
|
|
|
|
import time
|
|
|
|
|
|
from pathlib import Path
|
2025-07-26 03:36:54 +08:00
|
|
|
|
from typing import Any
|
|
|
|
|
|
from langchain_text_splitters import MarkdownTextSplitter
|
2025-07-23 19:21:45 +08:00
|
|
|
|
from src.utils import hashstr, get_docker_safe_url, logger
|
|
|
|
|
|
from src import config
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
def split_text_into_chunks(text: str, file_id: str, filename: str, params: dict = {}) -> list[dict]:
|
2025-07-23 19:21:45 +08:00
|
|
|
|
"""
|
2025-07-26 03:36:54 +08:00
|
|
|
|
将文本分割成块,使用 LangChain 的 MarkdownTextSplitter 进行智能分割
|
2025-07-23 19:21:45 +08:00
|
|
|
|
"""
|
|
|
|
|
|
chunks = []
|
2025-07-26 03:36:54 +08:00
|
|
|
|
chunk_size = params.get('chunk_size', 1000)
|
|
|
|
|
|
chunk_overlap = params.get('chunk_overlap', 200)
|
2025-07-23 19:21:45 +08:00
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
# 使用 MarkdownTextSplitter 进行智能分割
|
|
|
|
|
|
# MarkdownTextSplitter 会尝试沿着 Markdown 格式的标题进行分割
|
|
|
|
|
|
text_splitter = MarkdownTextSplitter(
|
|
|
|
|
|
chunk_size=chunk_size,
|
|
|
|
|
|
chunk_overlap=chunk_overlap,
|
|
|
|
|
|
)
|
2025-07-23 19:21:45 +08:00
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
text_chunks = text_splitter.split_text(text)
|
2025-07-23 19:21:45 +08:00
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
# 转换为标准格式
|
|
|
|
|
|
for chunk_index, chunk_content in enumerate(text_chunks):
|
|
|
|
|
|
if chunk_content.strip(): # 跳过空块
|
2025-07-23 19:21:45 +08:00
|
|
|
|
chunks.append({
|
|
|
|
|
|
"id": f"{file_id}_chunk_{chunk_index}",
|
2025-07-26 03:36:54 +08:00
|
|
|
|
"content": chunk_content.strip(),
|
2025-07-23 19:21:45 +08:00
|
|
|
|
"file_id": file_id,
|
|
|
|
|
|
"filename": filename,
|
|
|
|
|
|
"chunk_index": chunk_index,
|
|
|
|
|
|
"source": filename,
|
|
|
|
|
|
"chunk_id": f"{file_id}_chunk_{chunk_index}"
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
logger.debug(f"Successfully split text into {len(chunks)} chunks using MarkdownTextSplitter")
|
2025-07-23 19:21:45 +08:00
|
|
|
|
return chunks
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
def prepare_item_metadata(item: str, content_type: str, db_id: str) -> dict:
|
2025-07-23 19:21:45 +08:00
|
|
|
|
"""
|
|
|
|
|
|
准备文件或URL的元数据
|
|
|
|
|
|
"""
|
|
|
|
|
|
if content_type == "file":
|
|
|
|
|
|
file_path = Path(item)
|
|
|
|
|
|
file_id = f"file_{hashstr(str(file_path) + str(time.time()), 6)}"
|
|
|
|
|
|
file_type = file_path.suffix.lower().replace(".", "")
|
2025-08-05 22:19:55 +08:00
|
|
|
|
filename = file_path.name
|
|
|
|
|
|
item_path = os.path.relpath(file_path, Path.cwd())
|
2025-07-23 19:21:45 +08:00
|
|
|
|
else: # URL
|
|
|
|
|
|
file_id = f"url_{hashstr(item + str(time.time()), 6)}"
|
|
|
|
|
|
file_type = "url"
|
|
|
|
|
|
filename = f"webpage_{hashstr(item, 6)}.md"
|
|
|
|
|
|
item_path = item
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"database_id": db_id,
|
|
|
|
|
|
"filename": filename,
|
|
|
|
|
|
"path": item_path,
|
|
|
|
|
|
"file_type": file_type,
|
|
|
|
|
|
"status": "processing",
|
|
|
|
|
|
"created_at": time.time(),
|
|
|
|
|
|
"file_id": file_id
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-07-26 03:36:54 +08:00
|
|
|
|
def split_text_into_qa_chunks(text: str, file_id: str, filename: str,
|
|
|
|
|
|
qa_separator: None | str = None, params: dict = {}) -> list[dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
将文本按QA对分割成块,使用 LangChain 的 CharacterTextSplitter 进行分割"""
|
|
|
|
|
|
qa_separator = qa_separator or '\n\n'
|
|
|
|
|
|
text_chunks = text.split(qa_separator)
|
|
|
|
|
|
|
|
|
|
|
|
# 转换为标准格式
|
|
|
|
|
|
chunks = []
|
|
|
|
|
|
for chunk_index, chunk_content in enumerate(text_chunks):
|
|
|
|
|
|
if chunk_content.strip(): # 跳过空块
|
|
|
|
|
|
chunk_content = chunk_content.strip()[:4096]
|
|
|
|
|
|
chunks.append({
|
|
|
|
|
|
"id": f"{file_id}_qa_chunk_{chunk_index}",
|
|
|
|
|
|
"content": chunk_content.strip(),
|
|
|
|
|
|
"file_id": file_id,
|
|
|
|
|
|
"filename": filename,
|
|
|
|
|
|
"chunk_index": chunk_index,
|
|
|
|
|
|
"source": filename,
|
|
|
|
|
|
"chunk_id": f"{file_id}_qa_chunk_{chunk_index}",
|
|
|
|
|
|
"chunk_type": "qa" # 标识为QA类型的chunk
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
logger.debug(f"QA chunks: {chunks[0]}")
|
|
|
|
|
|
logger.debug(f"Successfully split QA text into {len(chunks)} chunks using CharacterTextSplitter with `{qa_separator=}`")
|
|
|
|
|
|
return chunks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_embedding_config(embed_info: dict) -> dict:
|
2025-07-23 19:21:45 +08:00
|
|
|
|
"""
|
|
|
|
|
|
获取嵌入模型配置
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
embed_info: 嵌入信息字典
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
2025-07-26 03:36:54 +08:00
|
|
|
|
dict: 标准化的嵌入配置
|
2025-07-23 19:21:45 +08:00
|
|
|
|
"""
|
|
|
|
|
|
config_dict = {}
|
|
|
|
|
|
|
2025-08-08 18:35:01 +08:00
|
|
|
|
try:
|
|
|
|
|
|
if embed_info:
|
|
|
|
|
|
config_dict['model'] = embed_info["name"]
|
|
|
|
|
|
config_dict['api_key'] = os.getenv(embed_info["api_key"], embed_info["api_key"])
|
|
|
|
|
|
config_dict['base_url'] = embed_info["base_url"]
|
|
|
|
|
|
config_dict['dimension'] = embed_info.get("dimension", 1024)
|
|
|
|
|
|
else:
|
|
|
|
|
|
from src.models import select_embedding_model
|
|
|
|
|
|
default_model = select_embedding_model(config.embed_model)
|
|
|
|
|
|
config_dict['model'] = default_model.model
|
|
|
|
|
|
config_dict['api_key'] = default_model.api_key
|
|
|
|
|
|
config_dict['base_url'] = default_model.base_url
|
|
|
|
|
|
config_dict['dimension'] = getattr(default_model, 'dimension', 1024)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"Error in get_embedding_config: {e}, {embed_info}")
|
|
|
|
|
|
raise ValueError(f"Error in get_embedding_config: {e}")
|
2025-07-23 19:21:45 +08:00
|
|
|
|
|
|
|
|
|
|
logger.debug(f"Embedding config: {config_dict}")
|
2025-07-26 03:36:54 +08:00
|
|
|
|
return config_dict
|