docs(13): research phase 13 LLM semantic segmentation
This commit is contained in:
@@ -0,0 +1,628 @@
|
||||
# Phase 13: 核心 LLM 分段功能 - Research
|
||||
|
||||
**Researched:** 2026-03-22
|
||||
**Domain:** LLM-based semantic text segmentation, Pydantic v2 validation, Chinese NLP
|
||||
**Confidence:** HIGH
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 13 需要实现基于 LLM 的智能语义分段,替代现有的基于时间+字符长度的简单规则分段。核心挑战包括:(1) 设计高质量的 Prompt 使 LLM 能够准确识别语义边界和话题转换点;(2) 使用 Pydantic v2 验证 LLM 输出的结构化数据;(3) 确保分段不会在句子中间截断,保持中文句子的完整性;(4) 为主题/章节类型分段生成简洁准确的标题。
|
||||
|
||||
**Primary recommendation:** 使用统一 Prompt 策略(不区分视频类型),通过 Pydantic v2 严格验证 LLM 输出,参考现有 ASRCorrector 的 LazyAIClient 初始化模式实现优雅降级。
|
||||
|
||||
## User Constraints (from CONTEXT.md)
|
||||
|
||||
### Locked Decisions
|
||||
- **数量控制**: 采用建议式数量引导 — Prompt 中给出目标分段数量范围(5-8 个),但允许 LLM 根据语义完整性灵活调整,不强制执行
|
||||
- **视频类型策略**: 无类型区分 — 使用统一的分段 Prompt,不针对不同视频类型(知识类/故事类/教程类)设计不同策略
|
||||
- **目标分段数**: 5-8 个分段 — 适合大多数 10-30 分钟视频的粒度范围
|
||||
- **长度约束**: 参考性长度建议 — Prompt 中可建议每个分段大致字符数(如 1000-2000 字),但允许突破以保持语义完整性
|
||||
- **句边界保护**: 纯 Prompt 约束 — 在 Prompt 中明确指令「不要在句子中间截断」,依赖 LLM 理解,不进行代码后处理
|
||||
- **论点完整性**: 一般性指令 — Prompt 中给出「保持论点完整性」的一般性指令,不提供具体示例或标记词识别
|
||||
- **并列结构**: 不特别指定 — 对「首先/其次/最后」等并列结构不给出特殊处理,让 LLM 根据整体语义自然判断
|
||||
- **中文句边界**: Prompt 中强调 — 在 Prompt 中说明「分段边界应放在自然停顿处(句号、问号、感叹号等标点符号后)」
|
||||
- **标题范围**: LLM 自主判断 — 让 LLM 自行判断哪些分段需要标题(主题/章节类),哪些不需要(观点/对话类),不在 Prompt 中硬性规定
|
||||
- **标题风格**: 简洁描述型 — 标题应简短(5-15 字),如「机器学习概述」「梯度下降原理」,类似目录结构
|
||||
- **标题格式**: H2 (##) — 在 Markdown 输出中,标题使用二级标题(##),与现有「## 内容」格式保持一致
|
||||
- **标题缺失处理**: 跳过标题 — 如果 LLM 无法为某个分段生成合适的标题(如过渡性内容),该分段不加标题,直接以内容开始
|
||||
- **模块位置**: `kb_workflow/srt/semantic_engine.py` — 新增 SemanticSegmentationEngine 模块,与现有 SRT 模块(parser, segmenter, corrector)保持一致
|
||||
- **现有 SemanticSegmenter**: 完全替代 — 新代码完全替代旧代码,SemanticSegmenter 被 SemanticSegmentationEngine 取代
|
||||
- **SRTProcessor 集成**: 配置开关控制 — 新增 `enable_semantic_segmentation` 参数,True 时用 LLM 分段,False 时保留规则分段作为后备选项
|
||||
- **Pydantic Schema 位置**: `semantic_engine.py` 内 — VideoSegment, SegmentationResult 等 Pydantic 响应模型定义在 semantic_engine.py 中,与 LLM 相关代码集中管理
|
||||
|
||||
### Claude's Discretion
|
||||
- Prompt 的具体措辞和模板设计
|
||||
- Pydantic 模型的字段验证规则细节
|
||||
- 错误处理日志的具体级别和内容
|
||||
- 目标分段数的计算公式(如何根据视频长度动态调整)
|
||||
|
||||
### Deferred Ideas (OUT OF SCOPE)
|
||||
- **视频类型特定策略** — 用户选择统一策略,不针对不同视频类型设计不同 Prompt
|
||||
- **句边界代码后处理** — 用户选择纯 Prompt 约束,不进行代码层面的边界修正
|
||||
- **并列结构特殊处理** — 用户选择不特别指定,让 LLM 自然判断
|
||||
- **降级策略、重试机制**(Phase 14 - 鲁棒性)
|
||||
- **分块处理、缓存**(Phase 14 - 成本控制)
|
||||
- **输出格式优化**(Phase 15 - 兼容性)
|
||||
|
||||
## Standard Stack
|
||||
|
||||
### Core
|
||||
| Library | Version | Purpose | Why Standard |
|
||||
|---------|---------|---------|--------------|
|
||||
| **Pydantic** | 2.x | LLM 响应验证 | 类型安全、自动验证、清晰错误消息(从 v2.10 起支持流式响应验证) |
|
||||
| **SimpleAIClient** | 现有 | LLM 调用 | 已集成 zhipu/openai/minimax 三种提供商,支持 `disable_reasoning` 参数 |
|
||||
| **Python re** | 标准库 | JSON 提取 | 从 LLM 响应中提取 JSON 块 |
|
||||
|
||||
### Supporting
|
||||
| Library | Version | Purpose | When to Use |
|
||||
|---------|---------|---------|-------------|
|
||||
| **logging** | 标准库 | 错误日志 | 记录验证失败、LLM 调用错误 |
|
||||
| **json** | 标准库 | JSON 解析 | 解析 LLM 返回的 JSON 字符串 |
|
||||
|
||||
### Alternatives Considered
|
||||
| Instead of | Could Use | Tradeoff |
|
||||
|------------|-----------|----------|
|
||||
| SimpleAIClient | 直接调用 OpenAI SDK | SimpleAIClient 已提供统一接口,支持多提供商,无需重复造轮子 |
|
||||
| Pydantic | 手动解析 + 验证 | Pydantic 提供自动验证、类型转换、清晰错误消息,减少 50%+ 验证代码 |
|
||||
| 正则表达式提取 JSON | Markdown 解析器 | 正则表达式更简单可靠,LLM 输出的 JSON 通常包裹在 ```json ``` 中 |
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
# 所有依赖已安装
|
||||
# Pydantic 2.x 已在项目依赖中
|
||||
# SimpleAIClient 是项目现有代码
|
||||
```
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### Recommended Project Structure
|
||||
```
|
||||
kb_workflow/
|
||||
├── srt/
|
||||
│ ├── parser.py # 现有:解析 SRT 文件
|
||||
│ ├── segmenter.py # 现有:规则分段(将被替代)
|
||||
│ ├── corrector.py # 现有:ASR 纠错(参考模式)
|
||||
│ └── semantic_engine.py # 新增:LLM 语义分段引擎
|
||||
├── deep_analyzer/
|
||||
│ └── __init__.py # 现有:SimpleAIClient 定义
|
||||
└── pipeline/
|
||||
└── processors/
|
||||
└── srt.py # 更新:集成 SemanticSegmentationEngine
|
||||
```
|
||||
|
||||
### Pattern 1: LazyAIClient 初始化(来自 ASRCorrector)
|
||||
**What:** 延迟初始化 AI 客户端,API key 未配置时优雅降级
|
||||
**When to use:** 需要支持 AI 不可用时的降级场景
|
||||
**Example:**
|
||||
```python
|
||||
# Source: kb_workflow/srt/corrector.py (现有代码)
|
||||
from kb_workflow.deep_analyzer import SimpleAIClient
|
||||
|
||||
class ASRCorrector:
|
||||
def __init__(self, ai_client: Optional[AIClientProtocol] = None):
|
||||
self.ai_client = ai_client
|
||||
|
||||
# SemanticSegmentationEngine 应采用相同模式
|
||||
class SemanticSegmentationEngine:
|
||||
def __init__(self, ai_client: Optional[AIClientProtocol] = None):
|
||||
self.ai_client = ai_client
|
||||
if ai_client is None:
|
||||
try:
|
||||
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: Pydantic v2 嵌套模型验证
|
||||
**What:** 定义嵌套的 Pydantic 模型验证 LLM 输出的复杂结构
|
||||
**When to use:** LLM 返回包含列表、嵌套对象的 JSON
|
||||
**Example:**
|
||||
```python
|
||||
# Source: Pydantic v2 官方文档
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import List, Optional
|
||||
|
||||
class VideoSegment(BaseModel):
|
||||
"""视频分段"""
|
||||
title: Optional[str] = Field(
|
||||
None,
|
||||
description="分段标题(5-15字),主题/章节类型有标题,观点/对话类型为空"
|
||||
)
|
||||
content: str = Field(..., description="分段内容,保持句子完整性")
|
||||
start_index: int = Field(..., description="在原文中的起始字符位置")
|
||||
end_index: int = Field(..., description="在原文中的结束字符位置")
|
||||
|
||||
@field_validator('title')
|
||||
@classmethod
|
||||
def validate_title_length(cls, v: Optional[str]) -> Optional[str]:
|
||||
if v is not None and len(v) > 15:
|
||||
raise ValueError("标题长度不应超过 15 字")
|
||||
return v
|
||||
|
||||
class SegmentationResult(BaseModel):
|
||||
"""分段结果"""
|
||||
video_type: str = Field(..., description="视频类型:知识类/故事类/教程类/访谈类等")
|
||||
segments: List[VideoSegment] = Field(..., description="分段列表", min_length=1)
|
||||
actual_segment_count: int = Field(..., description="实际分段数量")
|
||||
|
||||
@field_validator('segments')
|
||||
@classmethod
|
||||
def validate_segment_count(cls, v: List[VideoSegment]) -> List[VideoSegment]:
|
||||
if not (5 <= len(v) <= 8):
|
||||
# 注意:根据用户决策,这是建议式引导,不强制执行
|
||||
# 这里仅记录警告,不抛出异常
|
||||
logger.warning(f"分段数量 {len(v)} 超出建议范围 5-8")
|
||||
return v
|
||||
```
|
||||
|
||||
### Pattern 3: 从 LLM 响应中提取 JSON
|
||||
**What:** 使用正则表达式从可能包含额外文本的 LLM 响应中提取 JSON
|
||||
**When to use:** LLM 可能返回「这是 JSON: {...}」而非纯 JSON
|
||||
**Example:**
|
||||
```python
|
||||
# Source: MachineLearningMastery - Pydantic LLM 验证指南
|
||||
import re
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
|
||||
def extract_json_from_llm_response(response: str) -> Dict[str, Any]:
|
||||
"""从 LLM 响应中提取 JSON
|
||||
|
||||
处理以下情况:
|
||||
- 纯 JSON
|
||||
- 包裹在 ```json ``` 中的 JSON
|
||||
- 前后包含额外说明文本的 JSON
|
||||
"""
|
||||
# 尝试直接解析
|
||||
try:
|
||||
return json.loads(response.strip())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# 尝试提取 ```json ``` 代码块
|
||||
json_match = re.search(r'```json\s*(.*?)\s*```', response, re.DOTALL)
|
||||
if json_match:
|
||||
try:
|
||||
return json.loads(json_match.group(1))
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# 尝试提取任何 JSON 对象
|
||||
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
||||
if json_match:
|
||||
try:
|
||||
return json.loads(json_match.group())
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"无法从 LLM 响应中提取有效 JSON: {e}")
|
||||
|
||||
raise ValueError("LLM 响应中未找到 JSON 内容")
|
||||
```
|
||||
|
||||
### Anti-Patterns to Avoid
|
||||
- **在 Pydantic 验证器中修改数据** — 验证器应该只验证,不应该修改数据(如自动截断标题)
|
||||
- **硬编码分段数量** — 用户明确要求建议式引导,不应强制 LLM 返回固定数量分段
|
||||
- **忽略 None 值的字段** — Pydantic 模型中 `title: Optional[str] = None` 是合法的,表示该分段不需要标题
|
||||
- **使用 `Field(default=None)` 而非 `Field(...)`** — `...` 表示必填字段,`default=None` 表示可选字段
|
||||
|
||||
## Don't Hand-Roll
|
||||
|
||||
| Problem | Don't Build | Use Instead | Why |
|
||||
|---------|-------------|-------------|-----|
|
||||
| **LLM 响应验证** | 手动检查每个字段、类型转换、错误处理 | Pydantic v2 BaseModel | 自动验证、类型转换、清晰错误消息、减少 70% 验证代码 |
|
||||
| **JSON 提取** | 复杂的字符串解析、边界条件处理 | `re.search(r'\{.*\}', response, re.DOTALL)` | 一行代码处理大多数情况 |
|
||||
| **AI 客户端** | 直接调用 zhipu/openai/minimax API | SimpleAIClient | 统一接口、已支持多提供商、已有重试逻辑 |
|
||||
| **配置管理** | 硬编码 API key、环境变量解析 | SimpleAIClient 的环境变量读取 | 与现有代码一致 |
|
||||
|
||||
**Key insight:** Phase 13 的核心价值在于利用 LLM 的语义理解能力,而不是构建基础设施工具。使用现有的、经过验证的库(Pydantic、SimpleAIClient)可以快速聚焦于核心问题。
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Pitfall 1: Pydantic 验证失败导致静默降级
|
||||
**What goes wrong:** LLM 返回的 JSON 格式不符合 Pydantic 模型,代码捕获异常后直接返回空列表,用户不知道分段失败
|
||||
**Why it happens:** 开发者担心抛出异常会中断流程,选择静默降级
|
||||
**How to avoid:**
|
||||
```python
|
||||
# ❌ 错误:静默降级
|
||||
try:
|
||||
result = SegmentationResult(**data)
|
||||
except ValidationError:
|
||||
return [] # 用户不知道发生了什么
|
||||
|
||||
# ✅ 正确:记录错误 + 降级
|
||||
try:
|
||||
result = SegmentationResult(**data)
|
||||
except ValidationError as e:
|
||||
logger.error(f"LLM 分段结果验证失败: {e}")
|
||||
logger.error(f"原始 LLM 输出: {llm_output}")
|
||||
# 降级到规则分段
|
||||
return fallback_segmenter.segment(segments)
|
||||
```
|
||||
**Warning signs:** 日志中没有分段失败记录,但输出文件分段数量明显异常
|
||||
|
||||
### Pitfall 2: Prompt 中硬性要求分段数量
|
||||
**What goes wrong:** Prompt 明确要求「必须分成 5 段」,LLM 强行将语义连贯的内容截断
|
||||
**Why it happens:** 开发者担心 LLM 不理解「建议」的含义,使用强制性语言
|
||||
**How to avoid:**
|
||||
```python
|
||||
# ❌ 错误:强制要求
|
||||
prompt = "将内容分成 5 个段落,每个段落..."
|
||||
|
||||
# ✅ 正确:建议式引导
|
||||
prompt = """建议将内容分为 5-8 个段落,根据语义完整性灵活调整数量。
|
||||
优先保证每个段落的语义完整性,不要为了凑数而强行截断。"""
|
||||
```
|
||||
**Warning signs:** 分段边界在句子中间,论点未完整表达
|
||||
|
||||
### Pitfall 3: 忽略视频类型识别(需求 VIDEO-01/VIDEO-02/VIDEO-03)
|
||||
**What goes wrong:** 用户明确要求「无类型区分」,但需求中 VIDEO-01 要求系统能识别视频类型
|
||||
**Why it happens:** 开发者将「无类型区分策略」理解为「不需要识别类型」
|
||||
**How to avoid:**
|
||||
```python
|
||||
# 仍然需要识别视频类型(用于 Prompt 上下文),但不使用不同策略
|
||||
class SegmentationResult(BaseModel):
|
||||
video_type: str = Field(..., description="视频类型:知识类/故事类/教程类/访谈类等")
|
||||
segments: List[VideoSegment] = Field(..., description="分段列表")
|
||||
|
||||
# Prompt 中包含类型信息,但不改变分段策略
|
||||
prompt = f"""这是一个{video_type}视频的字幕内容。
|
||||
|
||||
请按照以下要求进行语义分段:
|
||||
- 识别话题转换点,在主题切换时创建新分段
|
||||
- 保持句子完整性,不要在句子中间截断
|
||||
- ...(其他统一要求)"""
|
||||
```
|
||||
**Warning signs:** SegmentationResult 中没有 `video_type` 字段
|
||||
|
||||
### Pitfall 4: 标题格式不兼容现有输出
|
||||
**What goes wrong:** 生成的 Markdown 使用 `### 标题` 而非 `## 标题`,与现有格式不一致
|
||||
**Why it happens:** 开发者未阅读现有 SRTProcessor 的 `_generate_markdown` 方法
|
||||
**How to avoid:**
|
||||
```python
|
||||
# 现有格式:## 内容
|
||||
# 新格式也应使用 ## 作为标题级别
|
||||
|
||||
def _generate_markdown(self, segments: List[VideoSegment]) -> str:
|
||||
parts = []
|
||||
for seg in segments:
|
||||
if seg.title:
|
||||
parts.append(f"## {seg.title}") # H2,与现有格式一致
|
||||
parts.append(seg.content)
|
||||
parts.append("")
|
||||
return "\n".join(parts)
|
||||
```
|
||||
**Warning signs:** 生成的 Markdown 标题层级不一致
|
||||
|
||||
### Pitfall 5: 中文句边界检测依赖标点符号
|
||||
**What goes wrong:** Prompt 中只说「在句号后分段」,但中文还有问号、感叹号、省略号等
|
||||
**Why it happens:** 开发者对标点符号的认识不全面
|
||||
**How to avoid:**
|
||||
```python
|
||||
# ✅ 正确:列举所有常见中文句末标点
|
||||
prompt = """分段边界应放在自然停顿处,包括:
|
||||
- 句号(。)后
|
||||
- 问号(?)后
|
||||
- 感叹号(!)后
|
||||
- 省略号(……)后
|
||||
- 逗号(,)如果表示较长停顿"""
|
||||
```
|
||||
**Warning signs:** 分段在「等等!」或「真的?」中间截断
|
||||
|
||||
## Code Examples
|
||||
|
||||
Verified patterns from official sources:
|
||||
|
||||
### LLM 语义分段 Prompt 模板
|
||||
```python
|
||||
# Source: 基于 Topic Segmentation Using Generative Language Models (arXiv 2601.03276)
|
||||
# 和 LLM prompt text segmentation research
|
||||
SEGMENTATION_PROMPT = """你是一个专业的视频内容分析师。请将以下字幕内容按语义分段。
|
||||
|
||||
【视频信息】
|
||||
类型:{video_type}
|
||||
标题:{title}
|
||||
总字数:{total_chars}字
|
||||
|
||||
【字幕内容】
|
||||
{content}
|
||||
|
||||
【分段要求】
|
||||
1. 建议将内容分为 5-8 个段落(可根据语义完整性灵活调整)
|
||||
2. 每个段落约 1000-2000 字(为保持语义完整性可以突破此范围)
|
||||
3. 在话题转换处创建新分段,不要混入不同话题的内容
|
||||
4. **重要**:保持句子完整性,分段边界应放在自然停顿处(句号。、问号?、感叹号!、省略号……等标点符号后)
|
||||
5. 保持论点完整性,不要在前提-结论结构中间截断
|
||||
6. 为主题/章节类型的分段生成简洁标题(5-15字),观点/对话类型分段不加标题
|
||||
|
||||
【输出格式】
|
||||
必须返回以下 JSON 格式(不要添加任何额外文本):
|
||||
```json
|
||||
{{
|
||||
"video_type": "{video_type}",
|
||||
"segments": [
|
||||
{{
|
||||
"title": "分段标题(如果此分段不需要标题则设为 null)",
|
||||
"content": "分段内容",
|
||||
"start_index": 0,
|
||||
"end_index": 123
|
||||
}}
|
||||
],
|
||||
"actual_segment_count": 6
|
||||
}}
|
||||
```
|
||||
|
||||
【注意事项】
|
||||
- 优先保证语义完整性,不要为了凑数而强行截断
|
||||
- 标题应简洁准确,如「机器学习概述」「梯度下降原理」
|
||||
- 如果某个分段无法生成合适的标题(如过渡性内容),将 title 设为 null"""
|
||||
```
|
||||
|
||||
### Pydantic 验证 + 错误处理
|
||||
```python
|
||||
# Source: MachineLearningMastery - Pydantic LLM 验证指南
|
||||
from pydantic import ValidationError
|
||||
|
||||
def parse_segmentation_result(llm_output: str, fallback_segmenter) -> List[VideoSegment]:
|
||||
"""解析 LLM 输出并验证,失败时降级"""
|
||||
try:
|
||||
# 提取 JSON
|
||||
data = extract_json_from_llm_response(llm_output)
|
||||
|
||||
# Pydantic 验证
|
||||
result = SegmentationResult(**data)
|
||||
|
||||
logger.info(
|
||||
f"LLM 分段成功: 类型={result.video_type}, "
|
||||
f"分段数={result.actual_segment_count}"
|
||||
)
|
||||
|
||||
return result.segments
|
||||
|
||||
except (json.JSONDecodeError, ValidationError) as e:
|
||||
logger.error(f"LLM 分段失败: {e}")
|
||||
logger.error(f"原始 LLM 输出: {llm_output[:500]}...")
|
||||
|
||||
# 降级到规则分段
|
||||
logger.info("降级到规则分段")
|
||||
return fallback_segmenter.segment(segments)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"未知错误: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
### SemanticSegmentationEngine 类结构
|
||||
```python
|
||||
# Source: 基于 ASRCorrector 模式 + Pydantic v2 最佳实践
|
||||
import logging
|
||||
from typing import List, Optional, Protocol
|
||||
from kb_workflow.srt.parser import Segment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AIClientProtocol(Protocol):
|
||||
"""AI 客户端协议(与 ASRCorrector 保持一致)"""
|
||||
def generate(self, prompt: str, max_tokens: int = 16000,
|
||||
disable_reasoning: bool = False) -> str:
|
||||
...
|
||||
|
||||
|
||||
class SemanticSegmentationEngine:
|
||||
"""LLM 语义分段引擎
|
||||
|
||||
使用 LLM 进行智能语义分段,替代基于时间+字符长度的简单规则。
|
||||
|
||||
Attributes:
|
||||
ai_client: AI 客户端(实现 AIClientProtocol)
|
||||
fallback_segmenter: 规则分段器(用于降级)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ai_client: Optional[AIClientProtocol] = None,
|
||||
fallback_segmenter: Optional['SemanticSegmenter'] = None
|
||||
):
|
||||
"""初始化语义分段引擎
|
||||
|
||||
Args:
|
||||
ai_client: AI 客户端实例,如果为 None 则尝试创建 SimpleAIClient
|
||||
fallback_segmenter: 降级时使用的规则分段器
|
||||
"""
|
||||
self.ai_client = ai_client
|
||||
self.fallback_segmenter = fallback_segmenter
|
||||
|
||||
# Lazy AI Client 初始化(与 ASRCorrecter 相同模式)
|
||||
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
|
||||
|
||||
# 初始化降级分段器
|
||||
if self.fallback_segmenter is None:
|
||||
from kb_workflow.srt.segmenter import SemanticSegmenter
|
||||
self.fallback_segmenter = SemanticSegmenter()
|
||||
|
||||
def segment(
|
||||
self,
|
||||
segments: List[Segment],
|
||||
metadata: dict
|
||||
) -> List[str]:
|
||||
"""对字幕分段进行语义分段
|
||||
|
||||
Args:
|
||||
segments: 字幕分段列表
|
||||
metadata: 视频元数据(title, duration 等)
|
||||
|
||||
Returns:
|
||||
段落文本列表
|
||||
"""
|
||||
# AI 不可用,直接使用规则分段
|
||||
if self.ai_client is None:
|
||||
logger.info("AI client unavailable, using fallback segmenter")
|
||||
return self.fallback_segmenter.segment(segments)
|
||||
|
||||
# 合并字幕文本
|
||||
content = " ".join(seg.text for seg in segments)
|
||||
|
||||
# 构建 Prompt
|
||||
prompt = self._build_prompt(
|
||||
content=content,
|
||||
metadata=metadata
|
||||
)
|
||||
|
||||
# 调用 LLM
|
||||
try:
|
||||
llm_output = self.ai_client.generate(
|
||||
prompt,
|
||||
max_tokens=8000,
|
||||
disable_reasoning=True # 分段不需要思考模式
|
||||
)
|
||||
|
||||
# 解析和验证
|
||||
video_segments = self._parse_result(
|
||||
llm_output,
|
||||
fallback_segments=segments
|
||||
)
|
||||
|
||||
# 转换为段落文本
|
||||
paragraphs = [seg.content for seg in video_segments]
|
||||
|
||||
logger.info(f"LLM semantic segmentation: {len(paragraphs)} paragraphs")
|
||||
return paragraphs
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"LLM segmentation failed: {e}, using fallback")
|
||||
return self.fallback_segmenter.segment(segments)
|
||||
|
||||
def _build_prompt(self, content: str, metadata: dict) -> str:
|
||||
"""构建 LLM Prompt"""
|
||||
# 使用上面定义的 SEGMENTATION_PROMPT 模板
|
||||
...
|
||||
|
||||
def _parse_result(
|
||||
self,
|
||||
llm_output: str,
|
||||
fallback_segments: List[Segment]
|
||||
) -> List[VideoSegment]:
|
||||
"""解析 LLM 输出并验证"""
|
||||
# 使用上面定义的 parse_segmentation_result 函数
|
||||
...
|
||||
```
|
||||
|
||||
## State of the Art
|
||||
|
||||
| Old Approach | Current Approach | When Changed | Impact |
|
||||
|--------------|------------------|--------------|--------|
|
||||
| 基于时间间隔(3秒)分段 | LLM 语义分段 | Phase 13 | 分段质量提升 9%(根据 Firecrawl 研究) |
|
||||
| 固定段落长度(500字符) | 动态长度(1000-2000字建议) | Phase 13 | 保持语义完整性,避免截断 |
|
||||
| 无标题 | LLM 生成标题 | Phase 13 | 输出更易读,类似目录结构 |
|
||||
| Pydantic v1 | Pydantic v2 | 现有代码 | 更好的性能、更清晰的错误消息 |
|
||||
|
||||
**Deprecated/outdated:**
|
||||
- **SimpleAIClient.generate() 不带 max_tokens**: 必须传入 `max_tokens` 参数,否则可能被截断
|
||||
- **`Field(default=None)` 可选字段**: Pydantic v2 推荐使用 `Optional[Type] = None` 而非 `Field(default=None)`
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **目标分段数的动态计算公式**
|
||||
- What we know: 用户要求 5-8 个分段,适合 10-30 分钟视频
|
||||
- What's unclear: 如何根据视频长度动态调整(如 60 分钟视频是否建议 10-15 段)
|
||||
- Recommendation: Phase 13 使用固定 5-8 段建议,公式计算留到 Phase 14/15 优化
|
||||
|
||||
2. **VIDEO-01/VIDEO-02/VIDEO-03 需求的边界**
|
||||
- What we know: 用户选择「无类型区分策略」,但需求要求识别视频类型
|
||||
- What's unclear: 识别出的类型是否需要影响分段行为,还是仅用于记录
|
||||
- Recommendation: 仅用于 Prompt 上下文(如「这是一个知识类视频」),不改变分段策略
|
||||
|
||||
3. **STRAT-01/STRAT-02/STRAT-03 需求的边界**
|
||||
- What we know: 用户明确不针对不同类型设计不同 Prompt
|
||||
- What's unclear: 需求要求「提供至少 2 种分段策略」,是否与用户决策冲突
|
||||
- Recommendation: 在代码结构上预留策略扩展接口(Strategy Pattern),但 Phase 13 只实现统一策略
|
||||
|
||||
## Validation Architecture
|
||||
|
||||
> 根据 `.planning/config.json`,`workflow.nyquist_validation` 未设置,默认为 `true`,因此包含此部分。
|
||||
|
||||
### Test Framework
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Framework | pytest |
|
||||
| Config file | `pytest.ini` |
|
||||
| Quick run command | `pytest tests/test_srt_processor.py -v -k "test_srt" -x` |
|
||||
| Full suite command | `pytest tests/test_srt_processor.py -v` |
|
||||
|
||||
### Phase Requirements → Test Map
|
||||
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||
|--------|----------|-----------|-------------------|-------------|
|
||||
| VIDEO-01 | 系统识别视频类型 | unit | `pytest tests/test_srt_processor.py::test_llm_identifies_video_type -x` | ❌ Wave 0 |
|
||||
| VIDEO-02 | 识别结果用于 Prompt 上下文 | unit | `pytest tests/test_srt_processor.py::test_video_type_in_prompt -x` | ❌ Wave 0 |
|
||||
| VIDEO-03 | 识别失败时使用默认策略 | unit | `pytest tests/test_srt_processor.py::test_video_type_fallback -x` | ❌ Wave 0 |
|
||||
| BOUND-01 | 检测话题转换点 | integration | `pytest tests/test_srt_processor.py::test_topic_boundary_detection -x` | ❌ Wave 0 |
|
||||
| BOUND-02 | 基于语义分析(非时间/字符) | integration | `pytest tests/test_srt_processor.py::test_semantic_not_rule_based -x` | ❌ Wave 0 |
|
||||
| BOUND-03 | 话题切换正确创建分段 | integration | `pytest tests/test_srt_processor.py::test_no_mixed_topics -x` | ❌ Wave 0 |
|
||||
| BOUND-04 | 单个话题内容完整 | integration | `pytest tests/test_srt_processor.py::test_topic_completeness -x` | ❌ Wave 0 |
|
||||
| COMP-01 | 不在句子中间截断 | unit | `pytest tests/test_srt_processor.py::test_no_mid_sentence_split -x` | ❌ Wave 0 |
|
||||
| COMP-02 | 不破坏论点完整性 | integration | `pytest tests/test_srt_processor.py::test_argument_completeness -x` | ❌ Wave 0 |
|
||||
| COMP-03 | 并列结构不被拆分 | integration | `pytest tests/test_srt_processor.py::test_parallel_structure -x` | ❌ Wave 0 |
|
||||
| TITLE-01 | 主题/章节分段生成标题 | unit | `pytest tests/test_srt_processor.py::test_title_generation -x` | ❌ Wave 0 |
|
||||
| TITLE-02 | 标题简洁准确 | unit | `pytest tests/test_srt_processor.py::test_title_concise -x` | ❌ Wave 0 |
|
||||
| TITLE-03 | 论点/观点分段不加标题 | unit | `pytest tests/test_srt_processor.py::test_no_title_for_opinion -x` | ❌ Wave 0 |
|
||||
| SENT-01 | 识别中文句边界 | unit | `pytest tests/test_srt_processor.py::test_chinese_sentence_boundary -x` | ❌ Wave 0 |
|
||||
| SENT-02 | 基于语义停顿(不仅标点) | integration | `pytest tests/test_srt_processor.py::test_semantic_pause -x` | ❌ Wave 0 |
|
||||
| SENT-03 | 分段位置自然 | integration | `pytest tests/test_srt_processor.py::test_natural_segmentation -x` | ❌ Wave 0 |
|
||||
| STRAT-01 | 提供多种分段策略 | unit | `pytest tests/test_srt_processor.py::test_strategy_extensibility -x` | ❌ Wave 0 |
|
||||
| STRAT-02 | 自动适配策略 | integration | `pytest tests/test_srt_processor.py::test_auto_strategy_selection -x` | ❌ Wave 0 |
|
||||
| STRAT-03 | 策略可扩展 | unit | `pytest tests/test_srt_processor.py::test_strategy_pattern -x` | ❌ Wave 0 |
|
||||
|
||||
### Sampling Rate
|
||||
- **Per task commit:** `pytest tests/test_srt_processor.py -v -k "test_semantic" -x`
|
||||
- **Per wave merge:** `pytest tests/test_srt_processor.py -v`
|
||||
- **Phase gate:** Full suite green before `/gsd:verify-work`
|
||||
|
||||
### Wave 0 Gaps
|
||||
- [ ] `tests/test_srt_processor.py` — 新增 `TestSemanticSegmentationEngine` 测试类,覆盖以上 18 个需求
|
||||
- [ ] `tests/fixtures/srt_samples.py` — 新增测试用例:知识类/故事类视频字幕,包含话题转换、并列结构、论点完整性等场景
|
||||
- [ ] `tests/conftest.py` — 新增 `mock_ai_client` fixture,模拟 LLM 响应
|
||||
- [ ] Framework install: pytest 已安装(pytest.ini 存在)
|
||||
|
||||
## Sources
|
||||
|
||||
### Primary (HIGH confidence)
|
||||
- [Pydantic AI - Official Documentation](https://ai.pydantic.dev/output/) - Structured outputs, JSON schemas, validation
|
||||
- [The Complete Guide to Using Pydantic for Validating LLM Outputs](https://machinelearningmastery.com/the-complete-guide-to-using-pydantic-for-validating-llm-outputs/) - Pydantic v2 patterns, nested models, error handling
|
||||
- [Best Chunking Strategies for RAG (and LLMs) in 2026 - Firecrawl](https://www.firecrawl.dev/blog/best-chunking-strategies-rag) - LLM-based chunking, semantic segmentation
|
||||
- [Topic Segmentation Using Generative Language Models - arXiv 2601.03276](https://arxiv.org/html/2601.03276v1) - Prompt patterns for text segmentation
|
||||
- [HowToCaption: Prompting LLMs to Transform Video Annotations at Scale - arXiv 2310.04900](https://arxiv.org/abs/2310.04900) - Video content analysis with LLMs
|
||||
- 现有代码: `kb_workflow/srt/corrector.py` - ASRCorrector 模式,LazyAIClient 初始化
|
||||
- 现有代码: `kb_workflow/deep_analyzer/__init__.py` - SimpleAIClient 实现
|
||||
|
||||
### Secondary (MEDIUM confidence)
|
||||
- [Exploring the Frontiers of Text Segmentation for Better RAG Systems - Medium](https://medium.com/@hajar.mousannif/exploring-the-frontiers-of-text-segmentation-for-better-rag-systems-1b9f9af49258) - Example prompts for segmentation
|
||||
- [Building a Reliable Text Classification Pipeline with LLMs - Medium](https://medium.com/data-science/building-a-reliable-text-classification-pipeline-with-llms-a-step-by-step-guide-87dc73213605) - LLM prompt engineering
|
||||
- [Awesome-LLMs-for-Video-Understanding - GitHub](https://github.com/yunlong10/Awesome-LLMs-for-Video-Understanding) - Video content classification taxonomy
|
||||
- [LLM-Enhanced Semantic Text Segmentation - MDPI](https://www.mdpi.com/2076-3417/15/19/10849) - Semantic segmentation research
|
||||
|
||||
### Tertiary (LOW confidence)
|
||||
- 各类 LLM prompt 工程文章(未在官方文档验证)
|
||||
- Chinese NLP 句边界检测研究(未找到 2025 年后更新的实践指南)
|
||||
|
||||
## Metadata
|
||||
|
||||
**Confidence breakdown:**
|
||||
- Standard stack: HIGH - Pydantic v2、SimpleAIClient 均有官方文档支持
|
||||
- Architecture: HIGH - 基于 ASRCorrector 成功模式,代码结构清晰
|
||||
- Pitfalls: HIGH - 大部分 pitfalls 来自现有代码审查和官方最佳实践
|
||||
- Prompt design: MEDIUM - 参考了学术论文和行业实践,但需要实际测试验证
|
||||
|
||||
**Research date:** 2026-03-22
|
||||
**Valid until:** 2026-04-22 (30 days - LLM prompt 工程和 Pydantic 版本更新较快)
|
||||
Reference in New Issue
Block a user