init: RAG 知识库服务初始提交
- 后端 API(Flask + Gunicorn) - RAG 引擎(混合检索 + 云端 Reranker + 引用溯源) - 文档解析(MinerU + 多格式支持) - Docker 生产部署配置 - 排除前端项目、敏感配置、模型文件
This commit is contained in:
361
knowledge/document_versions.py
Normal file
361
knowledge/document_versions.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
文档版本查询模块(简化版)
|
||||
|
||||
保留核心的版本查询功能,删除未使用的生命周期管理功能。
|
||||
|
||||
功能:
|
||||
1. 查询文档版本历史
|
||||
2. 获取当前生效版本
|
||||
3. 记录版本变更日志
|
||||
|
||||
使用方式:
|
||||
from knowledge.document_versions import DocumentVersionQuery
|
||||
|
||||
query = DocumentVersionQuery()
|
||||
|
||||
# 获取版本历史
|
||||
history = query.get_document_history("public_kb", "报销制度.pdf")
|
||||
|
||||
# 获取生效版本
|
||||
active = query.get_active_version("public_kb", "报销制度.pdf")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from data.db import get_connection
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ==================== 枚举与数据类 ====================
|
||||
|
||||
class DocumentStatus(Enum):
|
||||
"""文档状态"""
|
||||
DRAFT = "draft" # 草稿
|
||||
ACTIVE = "active" # 生效中
|
||||
DEPRECATED = "deprecated" # 已废止
|
||||
SUPERSEDED = "superseded" # 被替代
|
||||
|
||||
|
||||
@dataclass
|
||||
class DocumentVersionInfo:
|
||||
"""文档版本信息"""
|
||||
document_id: str
|
||||
collection: str
|
||||
version: str
|
||||
status: DocumentStatus
|
||||
effective_date: Optional[str] = None
|
||||
deprecated_date: Optional[str] = None
|
||||
deprecated_reason: Optional[str] = None
|
||||
change_summary: Optional[str] = None
|
||||
supersedes: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
chunk_count: int = 0
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
"document_id": self.document_id,
|
||||
"collection": self.collection,
|
||||
"version": self.version,
|
||||
"status": self.status.value if isinstance(self.status, DocumentStatus) else self.status,
|
||||
"effective_date": self.effective_date,
|
||||
"deprecated_date": self.deprecated_date,
|
||||
"deprecated_reason": self.deprecated_reason,
|
||||
"change_summary": self.change_summary,
|
||||
"supersedes": self.supersedes,
|
||||
"created_at": self.created_at,
|
||||
"created_by": self.created_by,
|
||||
"chunk_count": self.chunk_count
|
||||
}
|
||||
|
||||
|
||||
# ==================== 文档版本查询 ====================
|
||||
|
||||
class DocumentVersionQuery:
|
||||
"""文档版本查询(简化版)"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化"""
|
||||
pass
|
||||
|
||||
def get_document_history(
|
||||
self,
|
||||
collection: str,
|
||||
document_id: str,
|
||||
limit: int = 10
|
||||
) -> List[DocumentVersionInfo]:
|
||||
"""
|
||||
获取文档版本历史
|
||||
|
||||
Args:
|
||||
collection: 向量库名称
|
||||
document_id: 文档ID
|
||||
limit: 返回数量限制
|
||||
|
||||
Returns:
|
||||
版本信息列表(按时间倒序)
|
||||
"""
|
||||
try:
|
||||
with get_connection("knowledge") as conn:
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
document_id, collection, version, status,
|
||||
effective_date, deprecated_date, deprecated_reason,
|
||||
change_summary, supersedes, created_at, created_by,
|
||||
chunk_count
|
||||
FROM document_versions
|
||||
WHERE collection = ? AND document_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ?
|
||||
""",
|
||||
(collection, document_id, limit)
|
||||
)
|
||||
|
||||
versions = []
|
||||
for row in cursor.fetchall():
|
||||
versions.append(DocumentVersionInfo(
|
||||
document_id=row[0],
|
||||
collection=row[1],
|
||||
version=row[2],
|
||||
status=DocumentStatus(row[3]) if row[3] else DocumentStatus.ACTIVE,
|
||||
effective_date=row[4],
|
||||
deprecated_date=row[5],
|
||||
deprecated_reason=row[6],
|
||||
change_summary=row[7],
|
||||
supersedes=row[8],
|
||||
created_at=row[9],
|
||||
created_by=row[10],
|
||||
chunk_count=row[11] or 0
|
||||
))
|
||||
|
||||
return versions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取文档历史失败: {e}")
|
||||
return []
|
||||
|
||||
def get_active_version(
|
||||
self,
|
||||
collection: str,
|
||||
document_id: str
|
||||
) -> Optional[DocumentVersionInfo]:
|
||||
"""
|
||||
获取当前生效版本
|
||||
|
||||
Args:
|
||||
collection: 向量库名称
|
||||
document_id: 文档ID
|
||||
|
||||
Returns:
|
||||
生效版本信息,不存在则返回 None
|
||||
"""
|
||||
try:
|
||||
with get_connection("knowledge") as conn:
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
document_id, collection, version, status,
|
||||
effective_date, deprecated_date, deprecated_reason,
|
||||
change_summary, supersedes, created_at, created_by,
|
||||
chunk_count
|
||||
FROM document_versions
|
||||
WHERE collection = ? AND document_id = ? AND status = 'active'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(collection, document_id)
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return DocumentVersionInfo(
|
||||
document_id=row[0],
|
||||
collection=row[1],
|
||||
version=row[2],
|
||||
status=DocumentStatus(row[3]) if row[3] else DocumentStatus.ACTIVE,
|
||||
effective_date=row[4],
|
||||
deprecated_date=row[5],
|
||||
deprecated_reason=row[6],
|
||||
change_summary=row[7],
|
||||
supersedes=row[8],
|
||||
created_at=row[9],
|
||||
created_by=row[10],
|
||||
chunk_count=row[11] or 0
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取生效版本失败: {e}")
|
||||
return None
|
||||
|
||||
def get_next_version(self, collection: str, document_id: str) -> str:
|
||||
"""
|
||||
获取下一个版本号
|
||||
|
||||
Args:
|
||||
collection: 向量库名称
|
||||
document_id: 文档ID
|
||||
|
||||
Returns:
|
||||
版本号字符串,如 'v1', 'v2', 'v3'
|
||||
"""
|
||||
try:
|
||||
with get_connection("knowledge") as conn:
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
SELECT version FROM document_versions
|
||||
WHERE collection = ? AND document_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(collection, document_id)
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row and row[0]:
|
||||
# 解析现有版本号
|
||||
current = row[0]
|
||||
if current.startswith('v') and current[1:].isdigit():
|
||||
next_num = int(current[1:]) + 1
|
||||
else:
|
||||
# 非标准版本号,从1开始
|
||||
next_num = 1
|
||||
else:
|
||||
next_num = 1
|
||||
|
||||
return f"v{next_num}"
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"获取版本号失败: {e},使用默认 v1")
|
||||
return "v1"
|
||||
|
||||
def create_version_record(
|
||||
self,
|
||||
collection: str,
|
||||
document_id: str,
|
||||
version: str = None,
|
||||
status: str = "active",
|
||||
change_summary: str = "",
|
||||
supersedes: str = None,
|
||||
created_by: str = "",
|
||||
chunk_count: int = 0
|
||||
):
|
||||
"""
|
||||
创建版本记录
|
||||
|
||||
Args:
|
||||
collection: 向量库名称
|
||||
document_id: 文档ID
|
||||
version: 版本号(可选,不传则自动生成 v1, v2, v3...)
|
||||
status: 状态
|
||||
change_summary: 变更摘要
|
||||
supersedes: 替代的旧版本
|
||||
created_by: 创建者
|
||||
chunk_count: chunk数量
|
||||
"""
|
||||
# 自动生成版本号
|
||||
if version is None:
|
||||
version = self.get_next_version(collection, document_id)
|
||||
|
||||
try:
|
||||
with get_connection("knowledge") as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO document_versions
|
||||
(document_id, collection, version, status, effective_date,
|
||||
change_summary, supersedes, created_at, created_by, chunk_count)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
document_id,
|
||||
collection,
|
||||
version,
|
||||
status,
|
||||
datetime.now().isoformat(),
|
||||
change_summary,
|
||||
supersedes,
|
||||
datetime.now().isoformat(),
|
||||
created_by,
|
||||
chunk_count
|
||||
)
|
||||
)
|
||||
conn.commit()
|
||||
logger.info(f"创建版本记录: {collection}/{document_id} {version}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"创建版本记录失败: {e}")
|
||||
raise
|
||||
|
||||
def log_version_change(
|
||||
self,
|
||||
collection: str,
|
||||
document_id: str,
|
||||
change_type: str,
|
||||
old_version: str = None,
|
||||
new_version: str = None,
|
||||
old_status: str = None,
|
||||
new_status: str = None,
|
||||
reason: str = "",
|
||||
changed_by: str = ""
|
||||
):
|
||||
"""
|
||||
记录版本变更日志
|
||||
|
||||
Args:
|
||||
collection: 向量库名称
|
||||
document_id: 文档ID
|
||||
change_type: 变更类型(update/deprecate/restore)
|
||||
old_version: 旧版本号
|
||||
new_version: 新版本号
|
||||
old_status: 旧状态
|
||||
new_status: 新状态
|
||||
reason: 变更原因
|
||||
changed_by: 操作者
|
||||
"""
|
||||
try:
|
||||
with get_connection("knowledge") as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO version_change_logs
|
||||
(document_id, collection, old_version, new_version,
|
||||
old_status, new_status, change_type, reason, changed_by, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
document_id,
|
||||
collection,
|
||||
old_version,
|
||||
new_version,
|
||||
old_status,
|
||||
new_status,
|
||||
change_type,
|
||||
reason,
|
||||
changed_by,
|
||||
datetime.now().isoformat()
|
||||
)
|
||||
)
|
||||
conn.commit()
|
||||
logger.info(f"记录版本变更: {collection}/{document_id} {change_type}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"记录版本变更失败: {e}")
|
||||
|
||||
|
||||
# ==================== 工厂函数 ====================
|
||||
|
||||
_version_query_instance = None
|
||||
|
||||
def get_version_query() -> DocumentVersionQuery:
|
||||
"""获取文档版本查询实例(单例)"""
|
||||
global _version_query_instance
|
||||
if _version_query_instance is None:
|
||||
_version_query_instance = DocumentVersionQuery()
|
||||
return _version_query_instance
|
||||
Reference in New Issue
Block a user