- 后端 API(Flask + Gunicorn) - RAG 引擎(混合检索 + 云端 Reranker + 引用溯源) - 文档解析(MinerU + 多格式支持) - Docker 生产部署配置 - 排除前端项目、敏感配置、模型文件
362 lines
12 KiB
Python
362 lines
12 KiB
Python
"""
|
||
文档版本查询模块(简化版)
|
||
|
||
保留核心的版本查询功能,删除未使用的生命周期管理功能。
|
||
|
||
功能:
|
||
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
|