baf7c0f521
- compute_content_hash(): 计算 SHA256 内容哈希 - SemanticCache.get(): 从缓存读取 SegmentationResult - SemanticCache.set(): 保存 SegmentationResult 到缓存 - 支持版本控制(CACHE_VERSION = '1.0') - 缓存存储在 .planning/cache/semantic/ 目录 - 错误容错:缓存失败不影响主流程 - 自动创建缓存目录 遵守用户决策: - 缓存键: SHA256 内容哈希 - 缓存存储: 本地文件系统 - 缓存 TTL: 永久有效 - 缓存内容: 完整的 SegmentationResult Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
4.6 KiB
Python
156 lines
4.6 KiB
Python
"""语义分段缓存管理器
|
|
|
|
使用内容哈希(SHA256)作为缓存键,存储在本地文件系统。
|
|
|
|
功能:
|
|
- 计算内容的 SHA256 哈希作为缓存键
|
|
- 缓存 SegmentationResult 到本地文件系统
|
|
- 支持版本控制,便于未来格式变更
|
|
- 错误容错,缓存失败不影响主流程
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from pydantic import ValidationError
|
|
|
|
from kb_workflow.srt.semantic_engine import SegmentationResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def compute_content_hash(content: str) -> str:
|
|
"""计算内容的 SHA256 哈希作为缓存键
|
|
|
|
Args:
|
|
content: 字幕内容
|
|
|
|
Returns:
|
|
SHA256 哈希值(64 字符的十六进制字符串)
|
|
|
|
Examples:
|
|
>>> hash1 = compute_content_hash("相同内容")
|
|
>>> hash2 = compute_content_hash("相同内容")
|
|
>>> hash1 == hash2
|
|
True
|
|
>>> len(hash1)
|
|
64
|
|
"""
|
|
return hashlib.sha256(content.encode('utf-8')).hexdigest()
|
|
|
|
|
|
class SemanticCache:
|
|
"""语义分段结果缓存管理器
|
|
|
|
使用内容哈希(SHA256)作为缓存键,存储在本地文件系统。
|
|
|
|
缓存格式:
|
|
- 文件命名: {sha256_hash}.json
|
|
- 文件内容: JSON 格式的 SegmentationResult + version 字段
|
|
- 缓存 TTL: 永久有效,手动清理
|
|
|
|
Attributes:
|
|
CACHE_VERSION: 缓存格式版本,用于未来兼容性
|
|
DEFAULT_CACHE_DIR: 默认缓存目录
|
|
|
|
Examples:
|
|
>>> cache = SemanticCache()
|
|
>>> result = cache.get(subtitle_content)
|
|
>>> if result is None:
|
|
... result = llm_segment(subtitle_content)
|
|
... cache.set(subtitle_content, result)
|
|
"""
|
|
|
|
CACHE_VERSION = "1.0"
|
|
DEFAULT_CACHE_DIR = Path(".planning/cache/semantic")
|
|
|
|
def __init__(self, cache_dir: Optional[Path] = None):
|
|
"""初始化缓存管理器
|
|
|
|
Args:
|
|
cache_dir: 缓存目录路径,默认为 .planning/cache/semantic/
|
|
"""
|
|
self.cache_dir = cache_dir or self.DEFAULT_CACHE_DIR
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
def get(self, content: str) -> Optional[SegmentationResult]:
|
|
"""从缓存读取分段结果
|
|
|
|
Args:
|
|
content: 字幕内容
|
|
|
|
Returns:
|
|
缓存的 SegmentationResult,如果不存在或读取失败则返回 None
|
|
|
|
Examples:
|
|
>>> cache = SemanticCache()
|
|
>>> result = cache.get("字幕内容")
|
|
>>> if result:
|
|
... print(f"从缓存读取: {result.video_type}")
|
|
"""
|
|
cache_key = compute_content_hash(content)
|
|
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
|
|
if not cache_file.exists():
|
|
self.logger.debug(f"Cache miss: {cache_key}")
|
|
return None
|
|
|
|
try:
|
|
data = json.loads(cache_file.read_text(encoding='utf-8'))
|
|
|
|
# 验证版本
|
|
if data.get('version') != self.CACHE_VERSION:
|
|
self.logger.warning(
|
|
f"Cache version mismatch: {data.get('version')} != {self.CACHE_VERSION}"
|
|
)
|
|
return None
|
|
|
|
# 移除 version 字段,转换为 SegmentationResult
|
|
data.pop('version', None)
|
|
result = SegmentationResult(**data)
|
|
|
|
self.logger.debug(f"Cache hit: {cache_key}")
|
|
return result
|
|
|
|
except (json.JSONDecodeError, IOError, ValidationError) as e:
|
|
self.logger.warning(f"Failed to load cache: {e}")
|
|
return None
|
|
|
|
def set(self, content: str, result: SegmentationResult) -> None:
|
|
"""保存分段结果到缓存
|
|
|
|
Args:
|
|
content: 字幕内容
|
|
result: 分段结果
|
|
|
|
Examples:
|
|
>>> cache = SemanticCache()
|
|
>>> result = SegmentationResult(video_type="知识类", segments=[], actual_segment_count=0)
|
|
>>> cache.set("字幕内容", result)
|
|
"""
|
|
cache_key = compute_content_hash(content)
|
|
cache_file = self.cache_dir / f"{cache_key}.json"
|
|
|
|
try:
|
|
# 确保目录存在
|
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# 序列化为 JSON,添加 version 字段
|
|
data = result.model_dump()
|
|
data['version'] = self.CACHE_VERSION
|
|
|
|
cache_file.write_text(
|
|
json.dumps(data, ensure_ascii=False, indent=2),
|
|
encoding='utf-8'
|
|
)
|
|
|
|
self.logger.debug(f"Cache saved: {cache_key}")
|
|
|
|
except IOError as e:
|
|
self.logger.warning(f"Failed to save cache: {e}")
|