Files
rag/knowledge/document_versions.py
lacerate551 100d1a06eb init: RAG 知识库服务初始提交
- 后端 API(Flask + Gunicorn)
- RAG 引擎(混合检索 + 云端 Reranker + 引用溯源)
- 文档解析(MinerU + 多格式支持)
- Docker 生产部署配置
- 排除前端项目、敏感配置、模型文件
2026-06-04 17:35:27 +08:00

362 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
文档版本查询模块(简化版)
保留核心的版本查询功能,删除未使用的生命周期管理功能。
功能:
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