This repository has been archived on 2026-08-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
kb-skills/.planning/phases/14-robustness-cost/14-RESEARCH.md
T

20 KiB
Raw Blame History

Phase 14: 鲁棒性与成本控制 - Research

Research Date: 2026-03-23 Domain: LLM 容错处理、缓存优化、文本分块 Confidence: HIGH

Summary

本阶段专注于提高 LLM 语义分段的稳定性和成本效率。基于用户决策,实现轻量级的重试机制、智能缓存和长文本分块处理。核心原则是"快速失败,优雅降级"——AI 不可用时立即使用规则分段,保证系统可用性。

Primary Recommendation: 复用 ASRCorrector 的分批处理模式和 SimpleAIClient 的重试逻辑,避免引入新依赖,保持代码一致性。

<user_constraints>

User Constraints (from CONTEXT.md)

Locked Decisions

重试策略

  • 重试次数: 重试 1 次 — 失败后立即降级到规则分段,简单快速,避免浪费配额
  • 重试条件: 仅可恢复错误触发重试 — 网络错误/超时/限流重试,API 错误(如 400)直接降级
  • 重试延迟: 立即重试 — 失败后立即重试,大多数情况下网络抖动是瞬时的
  • 不可恢复错误: API 错误(4xx)、解析失败、验证失败等直接降级,不重试

分块处理

  • 分块阈值: 5000 字符(约 10-15 分钟字幕)— 与 ASRCorrector 的 batch_size 一致,保持代码模式统一
  • 分块方式: 按句子边界分割 — 在句号、问号、感叹号等标点符号后分割,保持语义完整性
  • 结果合并: 直接拼接各分块结果 — 每个分块独立分段后直接拼接,简单快速
  • 分块策略: 长字幕按句子边界分成多个 5000 字符以内的块,每块独立调用 LLM 分段

缓存机制

  • 缓存键: 字幕内容哈希(SHA256)— 精确匹配,相同内容一定命中
  • 缓存存储: 本地文件系统 — 存储在 .planning/cache/semantic/ 目录,简单持久化
  • 缓存 TTL: 永久有效 — 字幕内容不变,分段结果应该一致,手动清理过期缓存
  • 缓存内容: 完整的 SegmentationResult(JSON 格式),包含 video_type 和 segments

日志详细程度

  • 降级日志: 记录原因 + 降级方式 — 如 "API key 未配置,降级到规则分段"
  • 日志级别: 区分级别
    • 降级:warning 级别
    • 重试:info 级别
    • 成功:debug 级别
  • 重试日志: 记录重试原因和次数
  • 缓存日志: 记录缓存命中/未命中,使用 debug 级别

Claude's Discretion

  • 具体的重试逻辑实现细节
  • 缓存文件命名规则和目录结构
  • 分块算法的具体实现(句子边界检测)
  • 日志消息的具体措辞和格式

Deferred Ideas (OUT OF SCOPE)

  • 缓存清理命令: 自动清理过期缓存的功能 — 延后到未来版本,当前手动删除缓存文件
  • 缓存压缩: 压缩缓存文件以节省空间 — 延后到缓存文件较大时再考虑
  • 分块智能合并: 检测相邻分段语义相似度并合并 — 增加复杂度,当前直接拼接足够 </user_constraints>

<phase_requirements>

Phase Requirements

ID Description Research Support
ROBUST-01 LLM 调用失败时自动降级到简单规则分段 重试策略实现,异常时调用 fallback_segmenter
ROBUST-02 LLM 响应解析失败时自动重试或降级,有日志记录 区分可恢复/不可恢复错误,解析失败直接降级
ROBUST-03 AI 不可用时自动使用简单规则分段 Lazy AI Client 初始化,None 时直接使用规则分段
ROBUST-04 降级过程有日志记录,便于问题诊断 分级日志:降级 warning,重试 info,成功 debug
COST-01 长视频字幕自动分块处理,避免上下文窗口溢出 复用 ASRCorrector 分批模式,5000 字符阈值
COST-02 相同内容使用缓存结果,避免重复 LLM 调用 SHA256 哈希缓存键,本地文件系统存储
COST-03 提供配置开关,用户可选择是否启用 LLM 分段 SRTProcessor 已有 enable_semantic_segmentation 参数
</phase_requirements>

Standard Stack

Core

Library Version Purpose Why Standard
hashlib (built-in) - 内容哈希计算 Python 标准库,无需额外依赖
logging (built-in) - 分级日志记录 Python 标准库,已在项目中使用
json (built-in) - 缓存序列化 Python 标准库,已在项目中使用
pathlib (built-in) - 路径操作 Python 标准库,跨平台兼容

Supporting

Library Version Purpose When to Use
pydantic v2 响应验证 已在项目中使用,验证 SegmentationResult
requests 2.32.5+ HTTP 请求 已通过 SimpleAIClient 隐式依赖

Alternatives Considered

Instead of Could Use Tradeoff
简单重试循环 tenacity 库 tenacity 功能丰富但增加依赖,简单场景手写即可
文件系统缓存 redis/sqlite Redis 需要额外服务,sqlite 增加复杂度,文件系统最简单
手动分块 langchain TextSplitter langchain 体积大,ASRCorrector 模式已足够

Installation:

# 无需安装新依赖,全部使用 Python 标准库或已有依赖

Architecture Patterns

kb_workflow/srt/
├── semantic_engine.py           # 现有文件,添加重试、分块、缓存
├── cache/                       # 新增目录
│   ├── __init__.py
│   └── semantic_cache.py        # 缓存管理器
└── corrector.py                 # 参考分批处理模式(已有)

Pattern 1: Lazy Initialization with Fallback(已有模式)

What: 延迟初始化 AI Client,失败时使用规则分段 When to use: AI 依赖可能不可用的场景 Example:

# Source: kb_workflow/srt/semantic_engine.py:99-114
if self.ai_client is None:
    try:
        from kb_workflow.deep_analyzer import SimpleAIClient
        self.ai_client = SimpleAIClient()
        if not self.ai_client.api_key:
            logger.info("AI API key not configured")
            self.ai_client = None
    except Exception as e:
        logger.warning(f"Failed to initialize AI client: {e}")
        self.ai_client = None

Pattern 2: Batch Processing with Sentence Boundary Detection(复用模式)

What: 按句子边界分割长文本,每批独立处理 When to use: 文本超过 LLM 上下文窗口 Example:

# Source: kb_workflow/srt/corrector.py:125-136
# 按句子分割(简单分割,基于句号、问号、感叹号)
sentences = []
current = ""
for char in text:
    current += char
    if char in '。!?\n':
        sentences.append(current)
        current = ""

# 添加最后一个不完整的句子
if current:
    sentences.append(current)

Pattern 3: Content-Based Hashing(新增)

What: 使用 SHA256 哈希内容作为缓存键 When to use: 需要精确匹配相同内容 Example:

import hashlib

def compute_content_hash(content: str) -> str:
    """计算内容的 SHA256 哈希"""
    return hashlib.sha256(content.encode('utf-8')).hexdigest()

Pattern 4: Retry with Distinguishable Errors(新增)

What: 区分可恢复和不可恢复错误,仅对可恢复错误重试 When to use: API 调用可能因临时故障失败 Example:

import requests
from requests.exceptions import ConnectionError, Timeout, HTTPError

def is_recoverable_error(error: Exception) -> bool:
    """判断是否为可恢复错误"""
    if isinstance(error, (ConnectionError, Timeout)):
        return True
    if isinstance(error, HTTPError):
        status_code = error.response.status_code
        # 网络错误、超时、限流重试
        return status_code in (429, 500, 502, 503, 504)
    return False

def call_llm_with_retry(prompt: str, max_retries: int = 1):
    """带重试的 LLM 调用"""
    for attempt in range(max_retries + 1):
        try:
            return llm_client.generate(prompt)
        except Exception as e:
            if attempt < max_retries and is_recoverable_error(e):
                logger.info(f"LLM call failed (attempt {attempt + 1}): {e}, retrying...")
                continue
            # 最后一次失败或不可恢复错误,抛出异常
            raise

Anti-Patterns to Avoid

  • 不区分错误类型的盲目重试: 所有错误都重试会导致浪费配额,应区分可恢复/不可恢复错误
  • 固定延迟重试: 应立即重试(网络抖动是瞬时的),不要使用指数退避
  • 缓存键包含可变元数据: 缓存键应仅基于内容哈希,不应包含视频 ID 或时间戳
  • 过度复杂的分块合并: 直接拼接各分块结果即可,无需检测语义相似度

Don't Hand-Roll

Problem Don't Build Use Instead Why
内容哈希 自定义哈希算法 hashlib.sha256() 标准库函数,安全可靠
日志分级 print 语句 logging 模块 已在项目中使用,支持分级和输出控制
JSON 序列化 手动拼接字符串 json.dump()/json.load() 标准库函数,处理转义和格式
路径操作 字符串拼接 pathlib.Path 跨平台兼容,自动处理路径分隔符
重试装饰器 复杂的装饰器 简单 for 循环 简单场景(1 次重试)无需额外抽象

Key insight: Python 标准库提供了所需的全部基础设施,无需引入新依赖。重试、缓存、分块都是简单模式,过度工程化会增加维护成本。

Common Pitfalls

Pitfall 1: 不区分可恢复/不可恢复错误

What goes wrong: 所有 LLM 调用失败都重试,包括 API 错误(如 400 Bad Request),导致浪费配额和时间 Why it happens: 简单的 try-except 捕获所有异常,统一重试 How to avoid: 使用 is_recoverable_error() 函数判断错误类型,仅对网络错误、超时、限流进行重试 Warning signs: 看到 "重试 400 错误" 的日志

Pitfall 2: 缓存键冲突

What goes wrong: 不同内容的哈希意外相同(哈希碰撞),导致返回错误的缓存结果 Why it happens: 使用弱哈希算法或包含可变元数据(如时间戳) How to avoid: 使用 SHA256 强哈希,缓存键仅基于内容 Warning signs: 缓存命中后分段结果与内容不符

Pitfall 3: 分块边界破坏语义

What goes wrong: 在句子或段落中间分割,破坏内容完整性 Why it happens: 仅按字符数分割,不考虑句子边界 How to avoid: 复用 ASRCorrector 的分批模式,在标点符号(。!?\n)后分割 Warning signs: 分块后某些分段以不完整的句子开头

Pitfall 4: 缓存目录不存在导致写入失败

What goes wrong: 首次运行时缓存目录不存在,导致文件写入失败 Why it happens: 假设目录已存在,未创建父目录 How to avoid: 使用 pathlib.Path.mkdir(parents=True, exist_ok=True) 创建目录 Warning signs: FileNotFoundError 或 PermissionError

Pitfall 5: 降级日志信息不足

What goes wrong: 降级时仅记录 "降级到规则分段",无法诊断具体原因 Why it happens: 捕获异常后未记录原始错误信息 How to avoid: 记录具体错误类型和消息,如 "ConnectionError: 请求超时,降级到规则分段" Warning signs: 无法定位降级原因

Code Examples

Verified patterns from official sources:

可恢复错误检测

# Source: WebSearch 2026 - Python Retry Best Practices
from requests.exceptions import ConnectionError, Timeout, HTTPError

def is_recoverable_error(error: Exception) -> bool:
    """判断是否为可恢复错误

    可恢复错误:网络错误、超时、限流
    不可恢复错误:API 错误(4xx)、解析失败、验证失败
    """
    if isinstance(error, (ConnectionError, Timeout)):
        return True
    if isinstance(error, HTTPError):
        status_code = error.response.status_code
        # 429 Too Many Requests, 5xx Server Error
        return status_code in (429, 500, 502, 503, 504)
    return False

内容哈希缓存键

# Source: Python standard library documentation
import hashlib

def compute_cache_key(content: str) -> str:
    """计算内容的 SHA256 哈希作为缓存键"""
    return hashlib.sha256(content.encode('utf-8')).hexdigest()

缓存读写

# Source: Python standard library documentation
import json
from pathlib import Path

def load_cache(cache_key: str, cache_dir: Path) -> dict | None:
    """从文件加载缓存"""
    cache_file = cache_dir / f"{cache_key}.json"
    if not cache_file.exists():
        return None
    try:
        return json.loads(cache_file.read_text(encoding='utf-8'))
    except (json.JSONDecodeError, IOError) as e:
        logger.warning(f"Failed to load cache: {e}")
        return None

def save_cache(cache_key: str, data: dict, cache_dir: Path):
    """保存缓存到文件"""
    cache_dir.mkdir(parents=True, exist_ok=True)
    cache_file = cache_dir / f"{cache_key}.json"
    try:
        cache_file.write_text(json.dumps(data, ensure_ascii=False), encoding='utf-8')
    except IOError as e:
        logger.warning(f"Failed to save cache: {e}")

按句子边界分割

# Source: kb_workflow/srt/corrector.py:125-136
def split_by_sentences(text: str, max_length: int = 5000) -> list[str]:
    """按句子边界分割文本

    在句号、问号、感叹号后分割,保持语义完整性。
    """
    sentences = []
    current = ""
    for char in text:
        current += char
        if char in '。!?\n':
            sentences.append(current)
            current = ""

    if current:
        sentences.append(current)

    # 按最大长度分批
    batches = []
    current_batch = []
    current_length = 0

    for sentence in sentences:
        if current_length + len(sentence) > max_length:
            if current_batch:
                batches.append(''.join(current_batch))
            current_batch = [sentence]
            current_length = len(sentence)
        else:
            current_batch.append(sentence)
            current_length += len(sentence)

    if current_batch:
        batches.append(''.join(current_batch))

    return batches

State of the Art

Old Approach Current Approach When Changed Impact
盲目重试所有错误 区分可恢复/不可恢复错误 2026-03-23 (Phase 14) 减少配额浪费,提高响应速度
无缓存 SHA256 内容哈希缓存 2026-03-23 (Phase 14) 相同内容避免重复 LLM 调用
长文本直接调用 分块处理(5000字符) 2026-03-23 (Phase 14) 避免上下文窗口溢出
简单 print 日志 分级 logging 已在项目中使用 便于问题诊断和日志过滤

Deprecated/outdated:

  • 无区分的重试: 所有错误都重试的模式已被淘汰,应根据错误类型决定是否重试
  • 基于时间戳的缓存: 缓存键包含时间戳会导致相同内容缓存不命中,应仅基于内容哈希

Open Questions

  1. 分块后的分段索引如何计算?

    • What we know: 每个分块独立分段,结果直接拼接
    • What's unclear: VideoSegment.start_index 和 end_index 是相对于原文还是分块?
    • Recommendation: 相对于原文,需要在分块时记录每个分块的起始偏移量,分段后累加偏移量
  2. 缓存文件是否需要版本控制?

    • What we know: 缓存永久有效,手动清理
    • What's unclear: 如果 SegmentationResult 结构变化,旧缓存会加载失败
    • Recommendation: 在缓存 JSON 中添加 version 字段,加载时验证版本
  3. 分块处理的顺序是否影响结果?

    • What we know: 每个分块独立调用 LLM,结果直接拼接
    • What's unclear: 分块间是否有上下文依赖(如跨分块的论点)
    • Recommendation: 假设分块间独立,直接拼接。如果出现问题,在 Phase 15 考虑智能合并

Validation Architecture

Test Framework

Property Value
Framework pytest (python3 -m pytest)
Config file pytest.ini (已存在)
Quick run command python3 -m pytest tests/test_semantic_robustness.py -v -x
Full suite command python3 -m pytest tests/ -v

Phase Requirements → Test Map

Req ID Behavior Test Type Automated Command File Exists?
ROBUST-01 LLM 调用失败时自动降级 unit pytest tests/test_semantic_robustness.py::test_llm_failure_fallback -x ❌ Wave 0
ROBUST-02 解析失败时重试或降级 unit pytest tests/test_semantic_robustness.py::test_parse_failure_retry -x ❌ Wave 0
ROBUST-03 AI 不可用时使用规则分段 unit pytest tests/test_semantic_robustness.py::test_ai_unavailable_fallback -x ❌ Wave 0
ROBUST-04 降级日志记录 unit pytest tests/test_semantic_robustness.py::test_fallback_logging -x ❌ Wave 0
COST-01 长文本分块处理 unit pytest tests/test_semantic_robustness.py::test_long_text_chunking -x ❌ Wave 0
COST-02 缓存命中避免重复调用 unit pytest tests/test_semantic_robustness.py::test_cache_hit -x ❌ Wave 0
COST-03 配置开关控制 integration pytest tests/test_srt_processor.py::test_semantic_segmentation_toggle -x ❌ Wave 0

Sampling Rate

  • Per task commit: python3 -m pytest tests/test_semantic_robustness.py -v -x
  • Per wave merge: python3 -m pytest tests/ -v
  • Phase gate: Full suite green before /gsd:verify-work

Wave 0 Gaps

  • tests/test_semantic_robustness.py — 覆盖 ROBUST-01, ROBUST-02, ROBUST-03, ROBUST-04, COST-01, COST-02
  • tests/test_srt_processor.py — 覆盖 COST-03(配置开关)
  • tests/conftest.py — 共享 fixtures(mock LLM client, 样本字幕数据)
  • 框架安装: 无需安装,pytest 已配置

Mock Strategy: 使用 unittest.mock.patch 模拟 SimpleAIClient.generate(),测试不同失败场景(网络错误、解析失败、超时等)

Sources

Primary (HIGH confidence)

  • kb_workflow/srt/semantic_engine.py — 现有代码模式,UnifiedSegmentationStrategy, Lazy AI Client 初始化
  • kb_workflow/srt/corrector.py — 分批处理模式(_correct_long_text),按句子分割
  • kb_workflow/deep_analyzer/__init__.py — SimpleAIClient 重试逻辑(max_retries=3)
  • Python Standard Library — hashlib, logging, json, pathlib

Secondary (MEDIUM confidence)

Tertiary (LOW confidence)

Metadata

Confidence breakdown:

  • Standard stack: HIGH - 全部使用 Python 标准库或已有依赖
  • Architecture: HIGH - 基于现有代码模式(semantic_engine.py, corrector.py)
  • Pitfalls: HIGH - 基于用户决策和常见错误模式

Research date: 2026-03-23 Valid until: 2026-04-23 (30 days, 稳定技术栈)