Initial commit: 国土空间规划课程智能体 v1.0
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
文档处理服务(LangChain 1.0)
|
||||
"""
|
||||
import os
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from langchain_core.documents import Document as LangChainDocument
|
||||
|
||||
from ..models.document import Document, DocumentChunk
|
||||
from ..rag.vector_store import get_vector_store
|
||||
from ..rag.document_loaders import DocumentLoaderFactory
|
||||
from ..rag.text_splitters import get_text_splitter
|
||||
|
||||
class DocumentService:
|
||||
"""文档处理服务"""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.vector_store = get_vector_store()
|
||||
|
||||
async def process_document(self, document_id: int) -> bool:
|
||||
"""处理文档(使用LangChain 1.0)"""
|
||||
try:
|
||||
document = self.db.query(Document).filter(Document.id == document_id).first()
|
||||
if not document:
|
||||
return False
|
||||
|
||||
# 1. 使用LangChain加载文档
|
||||
documents = DocumentLoaderFactory.load_document(
|
||||
file_path=document.file_path,
|
||||
file_type=document.file_type,
|
||||
metadata={
|
||||
"document_id": document.id,
|
||||
"knowledge_base_id": document.knowledge_base_id,
|
||||
"title": document.title,
|
||||
"filename": document.filename
|
||||
}
|
||||
)
|
||||
|
||||
# 2. 使用中文优化的文本分割器
|
||||
text_splitter = get_text_splitter(chunk_size=1000, chunk_overlap=200)
|
||||
splits = text_splitter.split_documents(documents)
|
||||
|
||||
# 3. 添加到向量存储
|
||||
success = self.vector_store.add_documents(splits)
|
||||
|
||||
if success:
|
||||
document.is_processed = True
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理文档失败: {str(e)}")
|
||||
self.db.rollback()
|
||||
return False
|
||||
|
||||
def search_documents(self, query: str, knowledge_base_ids: Optional[List[int]] = None, limit: int = 5) -> List[Dict[str, Any]]:
|
||||
"""搜索文档(保留原有接口兼容性)"""
|
||||
try:
|
||||
# 构建过滤条件
|
||||
filter_dict = None
|
||||
if knowledge_base_ids:
|
||||
filter_dict = {"knowledge_base_id": {"$in": knowledge_base_ids}}
|
||||
|
||||
# 使用LangChain Chroma进行搜索
|
||||
results = self.vector_store.similarity_search_with_score(
|
||||
query=query,
|
||||
k=limit,
|
||||
filter=filter_dict
|
||||
)
|
||||
|
||||
# 格式化结果
|
||||
search_results = []
|
||||
for doc, distance in results:
|
||||
metadata = doc.metadata if hasattr(doc, 'metadata') else {}
|
||||
score = self._convert_distance_to_score(distance)
|
||||
|
||||
search_results.append({
|
||||
"content": doc.page_content,
|
||||
"metadata": metadata,
|
||||
"score": score,
|
||||
"distance": distance
|
||||
})
|
||||
|
||||
return search_results
|
||||
|
||||
except Exception as e:
|
||||
print(f"搜索文档失败: {str(e)}")
|
||||
return []
|
||||
|
||||
def _convert_distance_to_score(self, distance: float) -> float:
|
||||
"""将ChromaDB的distance转换为0-1范围的相似度分数"""
|
||||
import math
|
||||
|
||||
# 内积距离(负值)
|
||||
if distance < 0:
|
||||
return (1 + distance) / 2
|
||||
|
||||
# 大距离使用对数缩放
|
||||
if distance > 100:
|
||||
return 1 / (1 + math.log(distance))
|
||||
|
||||
# 标准距离转换
|
||||
return 1 / (1 + distance)
|
||||
|
||||
def get_document_chunks(self, document_id: int) -> List[DocumentChunk]:
|
||||
"""获取文档的所有块"""
|
||||
return self.db.query(DocumentChunk).filter(
|
||||
DocumentChunk.document_id == document_id
|
||||
).order_by(DocumentChunk.chunk_index).all()
|
||||
|
||||
def delete_document_chunks(self, document_id: int) -> bool:
|
||||
"""删除文档的所有块"""
|
||||
try:
|
||||
self.db.query(DocumentChunk).filter(
|
||||
DocumentChunk.document_id == document_id
|
||||
).delete()
|
||||
self.db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"删除文档块失败: {str(e)}")
|
||||
self.db.rollback()
|
||||
return False
|
||||
Reference in New Issue
Block a user